CyclicBarrier vs CountDownLatch: Who Waits? Java Concurrency Deep Dive
This article explains the key differences between Java's CyclicBarrier and CountDownLatch, focusing on which threads wait, thread lifecycle after signaling, reusability, and a practical pressure-test example using CountDownLatch to simulate concurrent requests.
Core Difference: Who Waits?
The article opens with a simple heuristic covering 80% of scenarios: CountDownLatch makes the main thread wait , while CyclicBarrier makes child threads wait for each other .
CountDownLatch: the main thread waits; CyclicBarrier: child threads wait.
In a typical CountDownLatch scenario, child threads call third‑party APIs and then countDown(); the main thread blocks on await() until the counter reaches zero, then aggregates results and returns.
With CyclicBarrier, each child thread performs a preliminary task, then calls await() at the barrier point. All threads must arrive before any can proceed — they mutually block until the last one arrives.
Thread Lifecycle After Signaling
A deeper distinction emerges when examining what happens after a thread signals:
CountDownLatch : the thread that calls countDown() finishes its job and becomes free — it either returns to a thread pool or terminates if created via new Thread(). It does not wait for others.
CyclicBarrier : every participating thread both works and waits. No thread can move past the barrier until all have arrived; they are symmetrically blocked.
Pressure‑Test Example: Simulating Concurrent Requests with CountDownLatch
The article demonstrates how to reverse the usual roles — making child threads wait on a latch initialized to 1, then having the main thread trigger them simultaneously to simulate a burst of 100 concurrent requests.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class StartExample {
public static void main(String[] args) throws InterruptedException {
// Core: a latch initialized to 1
CountDownLatch startSignal = new CountDownLatch(1);
int threadCount = 100;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
// 1. Submit 100 child tasks
for (int i = 0; i < threadCount; i++) {
final int threadId = i;
executor.submit(() -> {
try {
// Child threads block here because startSignal is 1
startSignal.await();
// When counter becomes 0, all 100 threads surge forward simultaneously
System.out.println("Child thread " + threadId + " bursts out, firing request!");
doHttpRequest();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// Main thread sleeps 2 seconds to ensure all children are parked at await()
Thread.sleep(2000);
System.out.println("============Main thread about to count down ============");
// 2. Main thread triggers the burst
startSignal.countDown();
executor.shutdown();
}
private static void doHttpRequest() {
// Simulate real business logic like product query
}
}Explanation of the flow:
Child threads start and immediately call startSignal.await(); because the counter is 1, all 100 threads park.
Main thread sleeps briefly to guarantee all children are waiting, then calls startSignal.countDown(), decrementing the counter to 0.
The AQS mechanism wakes all 100 threads almost simultaneously; they execute doHttpRequest() in a tight burst, simulating true concurrent load.
Advanced Distinction: Reusability and Symmetry
Beyond the waiting pattern, two structural differences matter:
CountDownLatch is one‑shot : once the counter hits zero it cannot be reset; the latch is spent.
CyclicBarrier is reusable : after all parties arrive, the barrier automatically resets for the next round (though the article notes this feature is rarely needed in practice).
The author summarizes the advanced view as:
CountDownLatch = unidirectional waiting. The signaling thread is free after countDown(); the waiting thread(s) only watch the counter. The relationship is asymmetric — whether main waits for children or children wait for main, the roles are fixed.
CyclicBarrier = mutual waiting. Every thread is both a worker and a waiter. No thread can proceed alone; they move forward together. The barrier resets automatically for subsequent cycles.
Interview‑Ready Takeaway
For interviews, articulate both levels:
Entry‑level intuition: main thread waits (latch) vs. child threads wait for each other (barrier).
Advanced: focus on freedom after signaling . Latch signalers are done; barrier participants are stuck until the last arrival. Latch is single‑use; barrier is cyclic.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
samdeepthink
Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
