A semaphore hands out a fixed number of permits, which is how you cap concurrent access to a scarce resource such as a connection pool.
var semaphore = new Semaphore(2);
var maxSeen = new AtomicInteger();
var inFlight = new AtomicInteger();
try (var pool = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10; i++) {
pool.submit(() -> {
semaphore.acquire();
try {
maxSeen.accumulateAndGet(inFlight.incrementAndGet(), Math::max);
Thread.sleep(Duration.ofMillis(10));
} finally {
inFlight.decrementAndGet();
semaphore.release();
}
return null;
});
}
}
System.out.println("permits = 2, peak concurrent = " + maxSeen.get());
permits = 2, peak concurrent = 2
Run this yourself in the Online Java Compiler, spin up a live REST API in the API Sandbox, or practise with Java interview questions.
Published 2026-07-30