Barriers & Latches
Making a group of threads meet at a point: the one-shot CountDownLatch, the reusable CyclicBarrier, the flexible Phaser, how a broken barrier releases everyone, and how to build a latch and a barrier from a condition variable.
Overview
Some coordination is not about protecting data but about timing a group. The main thread must not serve traffic until the database, the cache and the search index are all ready. In a simulation, no worker may start step 2 until every worker has finished step 1. Latches and barriers are the tools for these meeting points.
The two differ in who waits and whether they can be reused. A latch is a gate that opens once: some threads count it down, others wait for it to reach zero, and after that it stays open. A barrier is a meeting point for a fixed group: each member waits until all have arrived, then all go on together, and the barrier resets for the next round.
The family at a glance
- Java CountDownLatch(n) — Any threads calling await() — countDown() has been called n times, by anyone — No: the count cannot be reset — No
- Java CyclicBarrier(n, action) — The n parties themselves — All n have called await(); the action runs first — Yes, automatically after each round — No
- Java Phaser — Registered parties — All registered parties arrive at the current phase — Yes; phases are numbered — Yes: register(), arriveAndDeregister()
- Java Exchanger — Two threads — Both arrive; each receives the other's object — Yes — Always a pair
- Python threading.Barrier(n, action, timeout) — The n parties — All n have called wait() — Yes — No
- Python latch — Not in the standard library — Build it from a Condition (below)
The latch: a gate that opens once
The Java docs show the two classic uses. A latch of 1 is a start gate: workers wait on it, and the main thread counts it down once to release them all at the same moment. A latch of N is a finish line: each of N workers counts down once, and the main thread waits for zero.
Python has no latch, so here is one built from a condition variable, used exactly that way. The last check shows the one-shot behavior: counting down past zero changes nothing.
Java's CountDownLatch also gives a memory guarantee: everything a thread did before countDown() happens-before what another thread does after its await() returns. The same holds here because both sides use the same lock.
The barrier: rounds of work
A barrier suits iterative algorithms where each round reads what the previous round wrote. Without it, a fast worker would read a row that a slow worker is still writing. The barrier action, which runs once per round after the last thread arrives and before any is released, is the natural place for single-threaded work between rounds, such as swapping buffers.
The program splits a smoothing filter (each cell becomes the average of itself and its neighbors) across three threads for three rounds, double-buffered, and checks the result against a plain loop. It then shows the failure mode: when one party never arrives, the waiters time out and every waiter gets BrokenBarrierError instead of hanging.
Java's CyclicBarrier uses the same all-or-none rule: if one thread leaves early through an interrupt, a failure or a timeout, every other waiter leaves with BrokenBarrierException. A broken barrier stays broken until reset().
Phaser: a barrier whose group can change
A Phaser counts registered parties instead of taking a fixed number. Tasks can register() at any time and arriveAndDeregister() when they are done, and each completed phase increments the phase number. It can play either role: arrive() without waiting, like counting down a latch, or arriveAndAwaitAdvance(), like a barrier. When the last registered party deregisters, the default onAdvance terminates it.
- Wait once for N events, from any threads — CountDownLatch
- A fixed team repeating rounds — CyclicBarrier (Python: threading.Barrier)
- Rounds where workers join or finish at different times — Phaser
- Two stages swapping buffers (one fills, one drains) — Exchanger
- Wait for a set of submitted tasks to finish — ExecutorService.invokeAll or futures, not a latch
What you will be able to do
- Tell a latch from a barrier: who waits, who counts, and whether it resets.
- Use a latch as a start gate and as a finish line.
- Use a barrier with an action for round-based parallel algorithms, and explain what a broken barrier does.
- Know when a Phaser's dynamic registration is worth its complexity.
- Build a latch and a reusable barrier from a condition variable.
Interview questions
- What is a CountDownLatch and when do you use it?
- How does a CyclicBarrier differ from a CountDownLatch?
- What happens if one thread fails or times out while others wait at a barrier?
- Implement a reusable barrier from a lock and a condition variable.
- What is a Phaser and when would you use it over a CyclicBarrier?
Sign up free to read the full topic
More Low-Level Design topics
Start free on Cariara · Coding problems · System design studio