Synchronizers are the higher-level coordination primitives in java.util.concurrent — objects that let threads wait for each other instead of just guarding shared state. Where a lock answers "only one thread in here at a time," a synchronizer answers "wait until N things have happened" or "let at most K threads through." Almost all of them are thin state machines built on AbstractQueuedSynchronizer (AQS), so they share the same fair/non-fair queuing and interruptible/timed await semantics.
The five tools
The toolkit splits cleanly by what each one counts and whether it resets:
- CountDownLatch — a one-shot gate. Threads
await()until a counter reaches zero viacountDown(). Once open, it stays open; you throw it away after one use. - CyclicBarrier — a reusable rendezvous. A fixed number of threads call
await(); when the last arrives the barrier trips, optionally runs a barrier action, then resets for the next round. - Semaphore — a counter of permits.
acquire()blocks when no permits remain,release()returns one. Used to cap concurrency (a connection pool, a rate limiter). - Phaser — a reusable barrier with dynamic parties. Threads can register and deregister between phases, making it the right tool when the number of participants changes over time.
- Exchanger — a two-thread rendezvous that swaps objects. Each thread offers an item and receives the other's.
CountDownLatch N -> N-1 -> ... -> 0 (one-shot, never resets) CyclicBarrier parties arrive -> trip -> RESET -> repeat Semaphore permits: acquire-- / release++ (cap concurrency) Phaser parties register/deregister; advance phase 0,1,2... Exchanger thread A item <--> thread B item
Latch vs barrier: the classic confusion
The most-asked distinction: a latch waits for events and is one-directional and one-shot — one set of threads counts down while another set waits. A barrier waits for threads, where the same threads that arrive are the ones released, and it resets so you can reuse it across iterations. If a CyclicBarrier thread is interrupted or times out, every other waiter is released with a BrokenBarrierException; latches have no such "broken" state.
Pick by lifecycle: fixed count and single use → latch; recurring round-trip with a fixed crew → barrier; throttling access to a resource → semaphore; changing crew across rounds → phaser.