Mastering Java Synchronization Utilities: CountDownLatch, CyclicBarrier, Semaphore, Phaser, and Exchanger
This article explains how Java's synchronization utilities—CountDownLatch, CyclicBarrier, Semaphore, Phaser, and Exchanger—solve coordination problems such as one‑time startup waits, repeated barrier synchronization, resource‑quota limiting, dynamic multi‑stage collaboration, and two‑thread data exchange, with concrete code examples and a full seckill‑system case study.
CountDownLatch – One‑time Gate
CountDownLatch is created with an initial counter. Each worker thread invokes countDown() after finishing its task; a waiting thread calls await() to block until the counter reaches zero.
public class SeckillWarmup {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3); // three subsystems
AtomicReference<Throwable> failure = new AtomicReference<>();
new Thread(() -> {
try { loadStockToRedis(); }
catch (Throwable t) { failure.compareAndSet(null, t); }
finally { latch.countDown(); }
}).start();
new Thread(() -> {
try { initCouponRules(); }
catch (Throwable t) { failure.compareAndSet(null, t); }
finally { latch.countDown(); }
}).start();
new Thread(() -> {
try { loadRiskBlacklist(); }
catch (Throwable t) { failure.compareAndSet(null, t); }
finally { latch.countDown(); }
}).start();
latch.await(); // block until counter is zero
if (failure.get() != null) {
throw new IllegalStateException("Warmup failed", failure.get());
}
System.out.println("Warmup complete, opening gate");
openSeckillGate();
}
}The implementation reuses the AQS shared mode: the internal state field holds the counter. countDown() invokes releaseShared(), decrementing state and waking all waiting threads when it reaches zero. await() uses acquireSharedInterruptibly(), queuing the thread if state is non‑zero.
Key limitation : the counter can only decrement and cannot be reset; after reaching zero the latch stays open permanently. Therefore it is suitable only for one‑time coordination such as system startup.
Production code must place countDown() inside a finally block and propagate any exception via a separate failure channel; otherwise the waiting thread may proceed under a false assumption of success.
CyclicBarrier – Reusable Barrier
CyclicBarrier is appropriate when coordination must happen repeatedly—e.g., processing a batch of stock‑deduction requests where each batch must finish before the next begins.
public class BatchStockDeduction {
public static void main(String[] args) {
int workerCount = 4;
CyclicBarrier barrier = new CyclicBarrier(workerCount, () -> System.out.println("Batch completed, moving to next"));
AtomicBoolean cancelled = new AtomicBoolean();
for (int i = 0; i < workerCount; i++) {
int workerId = i;
new Thread(() -> {
for (int batch = 0; batch < 10 && !cancelled.get(); batch++) {
try {
processBatch(workerId, batch);
if (cancelled.get()) break;
barrier.await(); // wait for others
} catch (InterruptedException e) {
cancelled.set(true);
barrier.reset();
Thread.currentThread().interrupt();
break;
} catch (BrokenBarrierException e) {
cancelled.set(true);
break;
} catch (RuntimeException e) {
cancelled.set(true);
barrier.reset();
throw e;
}
}
}).start();
}
}
}CyclicBarrier is built on a ReentrantLock plus a Condition. Each thread calls await(), which locks, decrements an internal count, and if the count reaches zero executes an optional Runnable (the barrier action) before calling condition.signalAll(). The count is then automatically reset, enabling repeated use.
Boundary between CountDownLatch and CyclicBarrier
Waiting target : CountDownLatch waits for one or more external events ; CyclicBarrier waits for a group of cooperating threads to reach the same point.
Method callers : CountDownLatch has separate sets—task‑finishing threads call countDown(), waiting threads call await(); CyclicBarrier requires all participating threads to call the same await(), so waiters and awaited are the same set.
Reusability : CountDownLatch cannot be reused after the count reaches zero; CyclicBarrier resets automatically after release.
Post‑arrival action : CountDownLatch provides no built‑in callback; CyclicBarrier can accept an optional Runnable executed by the last arriving thread before release.
Semaphore – Controlling Concurrent Access
Semaphore manages a pool of permits. Threads acquire a permit before accessing a resource and release it afterward. It is also based on the AQS shared mode, where state represents the remaining permits.
public class SeckillRateLimiter {
private final Semaphore semaphore = new Semaphore(20); // limit DB concurrency
public void deductStock(Long productId) {
try {
semaphore.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return; // cannot release if acquire failed
}
try {
doDeductStock(productId);
} finally {
semaphore.release();
}
}
}Each successful acquire() must be paired with a corresponding release(). If a thread is interrupted while waiting, acquire() throws, and the code must avoid calling release() to prevent phantom permits.
Unlike a lock, a semaphore has no owner concept; any thread may release a permit it did not acquire, which makes it suitable for resource‑quota scenarios rather than mutual exclusion.
Phaser – Flexible Multi‑Stage Coordination (Brief)
Introduced in JDK 7, Phaser extends CyclicBarrier by allowing dynamic registration/deregistration of participants and hierarchical phases.
Dynamic registration/deregistration via register() / deregister() enables the participant count to change at runtime.
Supports hierarchical (multi‑level) phases to reduce contention when many participants are involved.
API is richer and more complex (methods such as arrive(), arriveAndAwaitAdvance(), awaitAdvance() and the concept of a phase number).
Used only when the number of participants changes during execution; otherwise CyclicBarrier is sufficient.
Exchanger – Two‑Thread Data Swap
Exchanger enables exactly two threads to meet at a synchronization point and swap objects.
Exchanger<List<Order>> exchanger = new Exchanger<>();
// Thread A
new Thread(() -> {
List<Order> buffer = fillOrderBuffer();
try {
List<Order> partner = exchanger.exchange(buffer);
process(partner);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// Thread B
new Thread(() -> {
List<Order> buffer = fillAnotherBuffer();
try {
List<Order> partner = exchanger.exchange(buffer);
process(partner);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();The exchange() call blocks both threads until the counterpart arrives, then swaps the buffers. This pattern reduces copying overhead when two roles have clearly defined data‑production and data‑consumption phases.
Practical Combined Example – Seckill System Warm‑up, Sharding, and Rate‑Limiting
public class SeckillSystem {
private final Semaphore dbSemaphore = new Semaphore(20); // DB concurrency limit
public void start() throws InterruptedException {
// Phase 1: CountDownLatch – wait for subsystem initialization
CountDownLatch warmupLatch = new CountDownLatch(3);
AtomicReference<Throwable> warmupFailure = new AtomicReference<>();
ExecutorService warmupPool = Executors.newFixedThreadPool(3);
submitWarmup(warmupPool, warmupLatch, warmupFailure, this::loadStockToRedis);
submitWarmup(warmupPool, warmupLatch, warmupFailure, this::initCouponRules);
submitWarmup(warmupPool, warmupLatch, warmupFailure, this::loadRiskBlacklist);
try { warmupLatch.await(); }
catch (InterruptedException e) { warmupPool.shutdownNow(); throw e; }
finally { warmupPool.shutdown(); }
if (warmupFailure.get() != null) {
throw new IllegalStateException("System warmup failed", warmupFailure.get());
}
System.out.println("Warmup complete, start processing");
// Phase 2: CyclicBarrier – batch processing with barrier sync
int workerCount = 4;
CyclicBarrier batchBarrier = new CyclicBarrier(workerCount,
() -> System.out.println("Batch finished, tally remaining stock"));
AtomicBoolean cancelled = new AtomicBoolean();
for (int i = 0; i < workerCount; i++) {
new Thread(() -> {
for (int batch = 0; batch < 100 && !cancelled.get(); batch++) {
try {
dbSemaphore.acquire();
try { deductStockForBatch(batch); }
finally { dbSemaphore.release(); }
if (cancelled.get()) break;
batchBarrier.await();
} catch (InterruptedException e) {
cancelled.set(true);
batchBarrier.reset();
Thread.currentThread().interrupt();
break;
} catch (BrokenBarrierException e) {
cancelled.set(true);
break;
} catch (RuntimeException e) {
cancelled.set(true);
batchBarrier.reset();
throw e;
}
}
}).start();
}
}
private void submitWarmup(ExecutorService pool, CountDownLatch latch,
AtomicReference<Throwable> failure, Runnable task) {
pool.execute(() -> {
try { task.run(); }
catch (Throwable t) { failure.compareAndSet(null, t); }
finally { latch.countDown(); }
});
}
}In this composite design: CountDownLatch handles the one‑time start‑up wait. CyclicBarrier synchronizes each processing batch. Semaphore protects the database from being overwhelmed.
Selection Summary
Scenario: wait for multiple one‑time tasks before proceeding → Tool: CountDownLatch → Reason: one‑time gate, AQS shared mode, permanent release at zero.
Scenario: group of threads repeatedly wait for each other before moving on → Tool: CyclicBarrier → Reason: reusable, lock+Condition implementation, supports optional barrier action.
Scenario: limit concurrent access to a resource → Tool: Semaphore → Reason: permit‑based quota, no owner concept, release may be performed by any thread.
Scenario: dynamic participant count in multi‑stage coordination → Tool: Phaser → Reason: supports dynamic register/deregister and hierarchical phases.
Scenario: two threads need to exchange data periodically → Tool: Exchanger → Reason: bidirectional blocking exchange, reduces copy overhead.
These utilities solve "thread rhythm" problems rather than mutual exclusion. Choose based on whether the waiting and awaited parties belong to the same thread group.
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.
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.
