A CountDownLatch is a one-shot gate where one or more threads wait for a set of events to complete; a CyclicBarrier is a reusable rendezvous where a fixed group of threads wait for each other. The two get confused constantly, so the clean mental model is: latch counts events and never resets; barrier counts threads and resets every round.
Side by side
| Operation | Best | Average | Worst | Note |
|---|---|---|---|---|
| Reusable | Latch: no, one-shot | Barrier: yes, resets on trip | core difference | |
| Waits for | Latch: events (countDown calls) | Barrier: threads (parties) arriving | events vs participants | |
| Who is released | Latch: separate waiter threads | Barrier: the same threads that arrived | barrier is mutual | |
| Built-in action | Latch: none | Barrier: optional Runnable on last arrival | barrier action | |
| Failure handling | Latch: independent, just blocks | Barrier: BrokenBarrierException for all | all-or-nothing | |
| Change the count | Latch: no | Barrier: no (use Phaser) | both fixed at construction |
The intuition
With a latch, the waiters and the counters are usually different threads: the main thread await()s while N workers each countDown() once and finish. It models "wait until N things have happened." After the count hits zero it is dead — you make a new one for the next batch.
With a barrier, the threads that call await() are the same ones that get released together. It models "everyone meet here before anyone moves on," and because it resets, it shines in iterative algorithms where the same crew synchronizes at the end of each phase.
// Latch: main waits for 3 independent workers to finish (one-shot)
CountDownLatch done = new CountDownLatch(3);
for (int i = 0; i < 3; i++)
pool.submit(() -> { try { work(); } finally { done.countDown(); } });
done.await();
// Barrier: 3 workers sync at the end of every iteration (reusable)
CyclicBarrier barrier = new CyclicBarrier(3, () -> mergeResults());
for (int i = 0; i < 3; i++)
pool.submit(() -> {
for (int phase = 0; phase < 10; phase++) {
computePhase(phase);
barrier.await(); // wait for the other two, then loop
}
});