Fundamentals 10 min read

Mastering CountDownLatch: How Java’s One‑Shot Latch Synchronizes Threads

CountDownLatch is a one‑time synchronization aid that blocks threads until a set of operations complete, illustrated with usage scenarios, a TestHarness example, and a deep dive into its internal implementation using AbstractQueuedSynchronizer, including code snippets and a flow diagram.

Ops Development Stories
Ops Development Stories
Ops Development Stories
Mastering CountDownLatch: How Java’s One‑Shot Latch Synchronizes Threads

Function Overview

CountDownLatch is a synchronization utility that delays thread progress until it reaches a terminal state. It behaves like a gate that remains closed until the latch reaches its end state, at which point it opens permanently, allowing all waiting threads to proceed.

Ensures a computation proceeds only after all required resources have been initialized. A binary latch can represent "resource R has been initialized" and forces operations that need R to wait on the latch.

Guarantees a service starts only after all its dependent services have started. Each service has a binary latch; the service waits on the latches of its dependencies before releasing its own latch.

In multiplayer scenarios, waits until all participants are ready before continuing execution.

CountDownLatch is a flexible latch implementation that can be used in the above cases. It maintains a counter initialized to a positive number representing the number of events to wait for. The countDown method decrements the counter, indicating an event has occurred, while the await method blocks until the counter reaches zero, or the waiting thread is interrupted or times out.

Usage Example

TestHarness demonstrates two common latch usages. It creates a fixed number of threads to execute a task concurrently, using two latches to represent a "starting gate" and an "ending gate". The starting gate is initialized to 1, and the ending gate to the number of worker threads. Each worker thread first waits on the starting gate, ensuring all threads are ready before execution begins. After completing its work, each thread calls countDown on the ending gate, allowing the main thread to efficiently wait until all workers finish and measure the elapsed time.

public class TestHarness {
    public long timeTasks(int nThreads, final Runnable task) throws InterruptedException {
        final CountDownLatch startGate = new CountDownLatch(1);
        final CountDownLatch endGate = new CountDownLatch(nThreads);
        for (int i = 0; i < nThreads; i++) {
            Thread t = new Thread(() -> {
                try {
                    startGate.await();
                    try {
                        task.run();
                    } finally {
                        endGate.countDown();
                    }
                } catch (InterruptedException ignored) {}
            });
            t.start();
        }
        long start = System.nanoTime();
        startGate.countDown();
        endGate.await();
        long end = System.nanoTime();
        return end - start;
    }
    public static void main(String[] args) throws InterruptedException {
        TestHarness testHarness = new TestHarness();
        AtomicInteger num = new AtomicInteger(0);
        long time = testHarness.timeTasks(10, () -> System.out.println(num.incrementAndGet()));
        System.out.println("cost time: " + time + "ms");
    }
}

Using a latch instead of starting threads immediately ensures that all threads begin execution simultaneously, providing a stable level of concurrency for accurate timing.

Usage Summary

CountDownLatch is one‑time use only; its counter is set in the constructor and cannot be reset. Once the latch has reached zero, it cannot be reused.

Source Code Analysis

Implementation Details

CountDownLatch is built on AbstractQueuedSynchronizer (AQS). The latch holds a sync object that extends AQS.

CountDownLatch startGate = new CountDownLatch(1);

The constructor creates a Sync instance with the initial count:

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}
Sync

is a static inner class extending AbstractQueuedSynchronizer. Its key methods are:

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

protected boolean tryReleaseShared(int releases) {
    for (;;) {
        int c = getState();
        if (c == 0) return false;
        int nextc = c - 1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

The await method simply delegates to AQS:

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

The countDown method releases the shared lock:

public void countDown() {
    sync.releaseShared(1);
}

When the counter reaches zero, releaseShared triggers doReleaseShared, which propagates the signal to waiting threads.

Flow Diagram

References

《Java 并发编程实战》

https://www.cnblogs.com/Lee_xy_z/p/10470181.html

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyAQSthread synchronizationCountDownLatch
Ops Development Stories
Written by

Ops Development Stories

Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.