Hook: Think of CyclicBarrier like a group photo line: nobody leaves until every person has arrived, and then the line resets for the next picture.
Question: What is CyclicBarrier in Java?
Answer: CyclicBarrier is a synchronization aid for a fixed number of threads that must all reach the same checkpoint before any of them can continue. Each thread calls await(); when the last one arrives, everyone is released together, and an optional barrier action runs once. It is reusable, so after one round finishes, the same barrier can be used again.
Interview-Ready Answer: In Java, CyclicBarrier lets a fixed number of threads wait for each other at a rendezvous point. I call await() from each thread, and when the last thread arrives, all threads are released together; I can also provide a barrier action that runs once per cycle. The key detail is that it is reusable, and if one thread is interrupted, times out, or the action fails, the barrier becomes broken and waiting threads get BrokenBarrierException.
CyclicBarrier lives in java.util.concurrent and is used when a group of worker threads must move through a problem in phases. The word cyclic means it can be used again and again, unlike one-shot tools. The word barrier means nobody crosses until the whole group has arrived.
await() at the checkpoint.BrokenBarrierException, and future calls to await() will also fail until you call reset().Use CyclicBarrier when the work is naturally split into phases and every worker must finish a phase before the next phase starts. Good examples are parallel image processing, simulation steps, batch analytics, and game rounds. It is a great fit when the number of workers is known and fixed, such as 4 CPU-bound tasks coordinated by a thread pool.
Do not use it for open-ended producer-consumer pipelines or situations where threads join and leave dynamically. In those cases, a different tool is a better match.
| Tool | Fixed parties | Reusable | Best for |
|---|---|---|---|
| CyclicBarrier | Yes | Yes | Phases that must sync |
| CountDownLatch | Yes | No | One-time start or finish gate |
| Phaser | No | Yes | Dynamic parties and phases |
The coordination work is effectively O(1) per arriving thread: one arrival, one count update, one possible wake-up. The barrier state itself is small and fixed, while the real cost is that threads may block and hold onto their stack and scheduling slot. In practice, barriers are common in small pools like 4, 8, 16, or 32 worker threads, and barrier actions should be short because they run on the critical path for the whole group.
Important details interviewers like: await() can return an arrival index, where the last thread gets 0 and earlier arrivals get larger numbers. Also, the barrier has no fairness guarantee about which waiting thread resumes first. Since Java 5, the API has been stable; the main behavior you must remember is the broken state and the fact that the barrier can be reused after a clean trip or after a reset.
await(long, TimeUnit) form.reset() does not wait politely for the current round to finish; it breaks the current generation and starts a new one.Imagine a checkout service that runs three independent checks in parallel: inventory availability, fraud scoring, and shipping eligibility. Each worker thread finishes its own task, then all three meet at a CyclicBarrier before the service decides whether the order can be placed. The barrier action can combine the partial results into one final decision.
What goes wrong when people misunderstand it? A team once assumed the barrier would quietly wait forever. One fraud-scoring call started taking 5 seconds, while the other workers used a 2-second timeout. Two threads timed out, the barrier became broken, and the third thread arrived later to find BrokenBarrierException. In production, this showed up as spikes in checkout latency, error logs full of broken-barrier messages, and users seeing spinning loaders followed by failed orders. The fix was to add sensible timeouts, monitor slow upstream calls, and keep the barrier action tiny.
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class CyclicBarrierDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("== Successful round ==");
runSuccessfulRound();
System.out.println();
System.out.println("== Broken round (timeout) ==");
runBrokenRound();
}
private static void runSuccessfulRound() throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(3, () ->
System.out.println("Barrier action: merging partial results once everyone arrives"));
ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 3; i++) {
final int id = i;
pool.submit(() -> workAndWait("Worker-" + id, barrier, 200L * id, false));
}
pool.shutdown();
pool.awaitTermination(5, TimeUnit.SECONDS);
}
private static void runBrokenRound() throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(3, () ->
System.out.println("This line should not appear because the barrier will break"));
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.submit(() -> workAndWait("A", barrier, 300, true));
pool.submit(() -> workAndWait("B", barrier, 400, true));
pool.submit(() -> workAndWait("C", barrier, 3500, true));
pool.shutdown();
pool.awaitTermination(6, TimeUnit.SECONDS);
System.out.println("Barrier broken after timeout? " + barrier.isBroken());
}
private static void workAndWait(String name, CyclicBarrier barrier, long beforeAwaitMs, boolean useTimeout) {
try {
Thread.sleep(beforeAwaitMs); // Independent work happens before the rendezvous point.
System.out.println(name + " reached the barrier");
int arrivalIndex;
if (useTimeout) {
// A timeout is a realistic safety net; one missing party should not block forever.
arrivalIndex = barrier.await(2, TimeUnit.SECONDS);
} else {
arrivalIndex = barrier.await();
}
System.out.println(name + " passed the barrier, arrival index = " + arrivalIndex);
} catch (TimeoutException e) {
System.out.println(name + " timed out waiting: " + e);
} catch (BrokenBarrierException e) {
System.out.println(name + " saw a broken barrier: " + e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println(name + " was interrupted");
}
}
}
Follow-up & Tricky Questions:
await() return? It returns the arrival index for that thread. The last thread to arrive gets 0, and earlier threads get larger numbers, which can be useful for choosing one thread to do extra work.CyclicBarrier be reused? Yes. That is the main difference from CountDownLatch: after a successful trip, the barrier resets automatically for the next cycle.InterruptedException, and the other waiting threads get BrokenBarrierException.Phaser instead? Choose Phaser when parties can register or deregister dynamically. CyclicBarrier is simpler, but only for a fixed number of participants.reset() wait for the current round to finish? No. It immediately breaks the current generation, so use it carefully in live code.Common Mistakes:
CountDownLatch is usually the simpler choice.Phaser instead.Memory Hook: Picture three hikers at a mountain checkpoint. Nobody moves to the next trail until all hikers arrive, and then the checkpoint opens again for the next leg of the journey.
Cheat Sheet:
await() blocks until all parties arrive.Phaser if party count changes.Practice Tasks:
await(1, TimeUnit.SECONDS) and observe the broken barrier path.