Concurrency

113. Concurrency Synchronizers: CountDownLatch, CyclicBarrier, and Semaphore

CountDownLatch (one-shot countdown), CyclicBarrier (reusable thread rendezvous), and Semaphore (permit-based rate limiter) solve thread coordination scenarios without manual wait/notify locks.

Java 21 (java.util.concurrent)

Explanation

Coordinating multiple concurrent threads without higher-level synchronizers requires complex wait()/notifyAll() loops prone to lost wakeups and deadlocks. The java.util.concurrent package provides three fundamental synchronizers:

1. CountDownLatch: A one-shot gate initialized with a count N. Threads call await() to block until other threads decrement the count to zero via countDown(). Once opened, the latch cannot be reset or reused. Canonical uses: waiting for all microservice dependencies to initialize, or starting a test where N threads release simultaneously. 2. CyclicBarrier: A reusable rendezvous point where a fixed number of threads (parties) wait for each other by calling await(). Once the barrier trip count is reached, an optional barrier action runs, all threads are released, and the barrier resets automatically for the next cycle. Canonical use: multi-phase parallel simulations or matrix computations. 3. Semaphore: Manages a pool of permits. Threads request permits via acquire() and return them via release(). If no permits are available, acquire() blocks. Canonical use: rate-limiting concurrent outbound HTTP calls or restricting database connection pools.

Example

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Semaphore;

public class SynchronizersDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1. CountDownLatch: Wait for 3 worker threads to finish
        CountDownLatch latch = new CountDownLatch(3);
        for (int i = 1; i <= 3; i++) {
            final int id = i;
            Thread.ofVirtual().start(() -> {
                System.out.println("Worker " + id + " completed initialization");
                latch.countDown();
            });
        }
        latch.await(); // Main thread blocks until count reaches 0
        System.out.println("All workers ready. Starting main workload!");

        // 2. CyclicBarrier: 2 threads rendezvous at phase checkpoint
        CyclicBarrier barrier = new CyclicBarrier(2, () -> 
            System.out.println(">>> Barrier reached! Proceeding to next phase...")
        );
        Runnable task = () -> {
            try {
                System.out.println(Thread.currentThread().getName() + " reached barrier");
                barrier.await();
            } catch (Exception e) { e.printStackTrace(); }
        };
        Thread t1 = new Thread(task, "Thread-A");
        Thread t2 = new Thread(task, "Thread-B");
        t1.start(); t2.start();
        t1.join(); t2.join();

        // 3. Semaphore: Rate-limit to max 2 concurrent operations
        Semaphore limiter = new Semaphore(2);
        Runnable restrictedAction = () -> {
            try {
                limiter.acquire();
                System.out.println(Thread.currentThread().getName() + " executing in permit");
                Thread.sleep(50);
            } catch (InterruptedException ignored) {
            } finally {
                limiter.release();
            }
        };
        for (int i = 0; i < 4; i++) Thread.ofVirtual().start(restrictedAction);
        Thread.sleep(200);
    }
}

Sources