Keyboard Shortcuts N Next post
P Previous post
S Save / unsave
R Read aloud
T Toggle theme
/ Focus search
Esc Close panels
🔥
Ready to read...
Java Java and Cryptography Mastery Series — Extended Java Programming Java Tutorial Module 19 - Advanced Collections and Concurrency

Java CountDownLatch for One Shot Waiting Coordination

Reviewed & accurate
AI Summary

Introduction

The Java CountDownLatch class was added in Java 5 (2004) as part of JSR 166. It is a synchroniser that allows one or more threads to wait until a set of operations being performed in other threads completes. The synchroniser is initialised with a positive count; the await() method blocks until the count reaches zero; the countDown() method decrements the count by one. Once the count reaches zero, the latch cannot be reset, which is what makes it a one-shot synchroniser. The java countdownlatch implementation is the standard primitive for service-startup coordination, test setup, and fan-in for parallel computations.

For a Java learner, CountDownLatch is the right tool when you need to wait for N independent things to finish before proceeding. The classic alternative is to join N threads, which works but is verbose and does not generalise to cases where the N things are not threads (for example, N actions executed by a pool of fewer than N workers). CountDownLatch decouples the number of waits from the number of threads. This post walks through the internal counter, the await semantics, and a working service-startup demo.

What is Java CountDownLatch?

The java countdownlatch class is a synchroniser built on a single volatile int counter protected by an AbstractQueuedSynchroniser (AQS) shared lock. The constructor takes a positive int and stores it as the initial count. The countDown() method decrements the count atomically; if the new count is zero, it releases all threads waiting on the shared lock. The await() method acquires the shared lock, which blocks while the count is greater than zero; when the count reaches zero, all waiting threads are released.

Conceptually, CountDownLatch belongs to the synchroniser family of java.util.concurrent, alongside Semaphore (which permits N concurrent holders and is reusable), CyclicBarrier (which waits for N threads to arrive and is reusable), and Phaser (which is a flexible, reusable barrier with phases). It is distinct from all three in being one-shot: once the count reaches zero, the latch stays open forever. If you need a reusable countdown, use a CyclicBarrier; if you need a flexible phase-based barrier, use a Phaser.

How Java CountDownLatch Works - Step by Step

Step 1: The AQS shared lock. CountDownLatch uses an AbstractQueuedSynchroniser implementation whose state field stores the count. The tryAcquireShared method returns 1 (success) when state equals 0 and -1 (retry) otherwise; the tryReleaseShared method decrements state via a CAS loop and returns true when state reaches 0, which signals AQS to release all waiting threads.

Step 2: A call to countDown(). The caller invokes sync.releaseShared(1), which calls tryReleaseShared. The implementation uses a CAS loop to decrement state: it reads the current state, computes the next state (current minus 1), and CAS-updates state to the next state. If the CAS fails because another thread beat it, the loop retries. If the new state is 0, the method returns true, which tells AQS to release all threads waiting on the shared lock.

Step 3: A call to await(). The caller invokes sync.acquireSharedInterruptibly(1), which calls tryAcquireShared. If state is 0, the method returns 1, and await returns immediately. If state is greater than 0, the method returns -1, and AQS parks the calling thread on the synchroniser's wait queue. The thread stays parked until state reaches 0 (at which point AQS releases all parked threads) or until the thread is interrupted.

Step 4: The timed variant await(timeout, unit) parks the calling thread for at most a given duration. If the duration expires before the count reaches 0, the method returns false; otherwise it returns true. This is the right choice when the caller cannot block forever, for example in a service-startup timeout.

Step 5: Once the count reaches 0, the latch stays open forever. Subsequent countDown() calls are no-ops (the count cannot go below 0), and subsequent await() calls return immediately. This is the one-shot semantic: the latch is a one-way door from closed to open, with no reset. To reuse, discard the latch and construct a new one.

Java Program for CountDownLatch

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        int serviceCount = 3;
        CountDownLatch ready = new CountDownLatch(serviceCount);
        AtomicInteger started = new AtomicInteger();

        Runnable service = () -> {
            String name = Thread.currentThread().getName();
            int id = started.incrementAndGet();
            try {
                Thread.sleep((long) (200 * id)); // pretend to start up
                System.out.println(name + " ready");
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
                return;
            } finally {
                ready.countDown();
            }
        };

        ExecutorService pool = Executors.newFixedThreadPool(serviceCount);
        for (int i = 0; i < serviceCount; i++) pool.submit(service);

        // Main thread waits for all services to be ready
        boolean allReady = ready.await(5, TimeUnit.SECONDS);
        System.out.println("all services ready? " + allReady);

        pool.shutdown();
        pool.awaitTermination(5, TimeUnit.SECONDS);
        System.out.println("started counter = " + started.get());
        System.out.println("pool terminated");
    }
}

Code Explanation - Line by Line

The main method constructs a CountDownLatch with initial count 3, equal to the number of services that must become ready before the main thread proceeds. The AtomicInteger started counts how many service tasks actually began running, which is useful for the final sanity check that confirms all three tasks ran.

The service Runnable simulates a startup delay that grows with the task id (200, 400, and 600 milliseconds respectively). The Thread.sleep call stands in for whatever real startup work the service does (loading config, opening sockets, warming caches). The InterruptedException catch follows the canonical pattern: restore the interrupt flag and return early.

The finally block calls ready.countDown() unconditionally. Putting countDown in finally is the standard idiom because it ensures that the latch is decremented even if the service throws an exception; otherwise the main thread would wait forever for a service that has died. Every service that starts must eventually count down, regardless of success or failure.

The thread pool spins up three service tasks. The submit order is irrelevant because the pool runs them on three threads in parallel. The main thread then calls ready.await(5, TimeUnit.SECONDS), which blocks until the count reaches 0 or the 5-second timeout expires. With startup delays of 200, 400, and 600 milliseconds, the timeout is generous; the await should return true in about 600 milliseconds.

The shutdown and awaitTermination sequence ensures the pool has finished before the main thread prints the final report. The started counter should be 3 (all three service tasks started), and the "pool terminated" message should appear. If a service had been cancelled before it could start, the started counter would be less than 3, and the main thread's await would have timed out.

Sample Input and Output

Input:
  CountDownLatch initial count = 3
  Three service tasks with startup delays 200, 400, 600 ms
  Main thread awaits with 5-second timeout

Output:
  pool-1-thread-2 ready
  pool-1-thread-3 ready
  pool-1-thread-1 ready
  all services ready? true
  started counter = 3
  pool terminated

The order of the "ready" prints depends on the OS thread scheduler, but the slowest service (the one with 600-millisecond delay) sets the wait time. The await returns true because all three services finished within the 5-second timeout. The started counter is 3, confirming that every service actually ran. The pool terminates cleanly.

Common Mistakes to Avoid

Mistake 1: Putting countDown() outside the finally block. If the service throws an exception before calling countDown, the latch never reaches zero and the main thread waits forever. Always put countDown in a finally block to guarantee that the latch is decremented regardless of success or failure.

Mistake 2: Reusing a CountDownLatch after it has reached zero. The latch is one-shot: once the count is zero, it stays zero. Calling countDown() on a zero-count latch is a no-op; calling await() returns immediately. If you need a reusable barrier, use CyclicBarrier or Phaser instead.

Mistake 3: Initialising the latch with the wrong count. If you set the count higher than the number of countDown calls you will actually make, the await will block forever (or until timeout). If you set it lower, the await will return early, before all the operations have actually finished. Always audit the count carefully, especially when the count is computed dynamically.

Mistake 4: Holding a long-running operation between countDown and the end of the task. The countDown method should be the last thing the task does. If you put it before some long cleanup, the main thread will think the task is done when it is not. Put it in the finally block at the very end.

Real-World Applications

CountDownLatch is the standard primitive for service-startup coordination. Application servers that start many subsystems in parallel (database pool, message broker, network listeners, scheduler) use a CountDownLatch to wait for all subsystems to report ready before opening the front door to client traffic. The same pattern appears in Spring Boot's startup orchestration.

Test setup in JUnit and TestNG. A test that depends on N external resources (say, N microservice endpoints) can wait on a CountDownLatch until all N have responded to a health check. The JUnit 5 @BeforeAll combined with a parallel resource initialiser often uses a latch internally; many testing libraries expose the pattern as a public utility.

Fan-in for parallel computations. A computation that splits work across N threads and must wait for all N to finish before reducing the results uses a CountDownLatch for the fan-in. ForkJoinTask already does this internally, but for tasks that don't fit the ForkJoinPool model (for example, blocking I/O across many endpoints), CountDownLatch is the standard fan-in primitive.

Database migration orchestration. A migration script that applies N changes in parallel and waits for all N to complete before announcing the migration done uses a CountDownLatch. The same pattern appears in deploy scripts that wait for N services to come up before announcing the deploy complete. The one-shot semantic is exactly right: the deploy is either done or not, with no need to reset.

Conclusion

The java countdownlatch class is a one-shot synchroniser that allows one or more threads to wait until N countDown calls have been made. It is the standard primitive for service-startup coordination, test setup, and fan-in for parallel computations. To go further, study CyclicBarrier for the reusable barrier variant and Phaser for the flexible phase-based variant.

Test Your Knowledge
How did you find this?

Comments

Join the discussion! Sign in with your Google or Blogger account, or comment as Anonymous - no account needed. For quick questions, also reach me on Telegram @cytestch.

Comments