Concurrency
Synchronizers: Latches, Barriers, Semaphores
Coordination primitives for thread rendezvous:
CountDownLatch (wait for N events, once), CyclicBarrier (N parties meet repeatedly), Semaphore (at most N concurrent holders), Phaser (all of the above, dynamic).CountDownLatch(n):await()blocks untilcountDown()reaches zero; single-useCyclicBarrier(n, action): partiesawait()each other; reusable per generationSemaphore(permits):acquire/releasebound concurrent access to a resourcePhaser: register/deregister parties dynamically across phases- Prefer these over
wait/notify— always (EJ 81)
CountDownLatch start = new CountDownLatch(1);
CountDownLatch done = new CountDownLatch(workers);
for (int i = 0; i < workers; i++) {
pool.submit(() -> {
start.await(); // everyone waits for the gun
try { work(); }
finally { done.countDown(); }
return null;
});
}
long t0 = System.nanoTime();
start.countDown(); // fire!
done.await(); // wait for all to finish
long elapsed = System.nanoTime() - t0;private final Semaphore dbSlots = new Semaphore(10); // at most 10 concurrent queries
public Result query(String sql) throws InterruptedException {
dbSlots.acquire();
try {
return db.run(sql);
} finally {
dbSlots.release();
}
}Semaphores are the general resource-bounding tool: connection limits, rate limiting, turning any collection into a bounded one (JCiP 5.5.3). With Virtual Threads they replace pool-sizing as the way to limit concurrent access to a constrained resource — a million virtual threads, ten permits.
CyclicBarrier suits iterative simulations: N workers compute a step, await() (the barrier action merges results), then everyone proceeds to the next generation. Exchanger<V> swaps objects between exactly two threads — the classic buffer-swap between a filler and a drainer.