CountDownLatch in Java

Source: Jira KAN-159 and KAN-160, both titled "CountDownLatch in Java" (duplicates, handled as one article).

KAN-159 points to Baeldung's

Guide to CountDownLatch in Java.

Abbreviations

Abbreviation Full name
JUC java.util.concurrent package
AQS AbstractQueuedSynchronizer
JDK Java Development Kit
CAS Compare-And-Swap
API Application Programming Interface

What it is

java.util.concurrent.CountDownLatch (in JUC since JDK 5) is a synchronizer that lets one or more threads wait

until a set of operations performed by other threads has completed. Think of it as a counter that only goes down:

Method Behaviour
new CountDownLatch(int count) Starts at count; a negative value throws IllegalArgumentException
void countDown() Decrements the count; when it reaches 0, releases every waiting thread. No-op at 0
void await() Blocks until the count is 0 (or the thread is interrupted)
boolean await(long timeout, TimeUnit unit) As above, but returns false if the timeout elapses first
long getCount() Current count, for diagnostics

Two properties shape how it is used:

  • It is one-shot. Once the count hits zero it cannot be reset; later await() calls return immediately.
    Need a reusable barrier? Use CyclicBarrier or Semaphore.
  • Anyone can count down. There is no ownership (unlike a lock), so the thread that calls countDown() does
    not have to be the one that will be waited on.

Internally it is a thin wrapper over AQS (AbstractQueuedSynchronizer) in shared mode: the AQS state is the

count, countDown() decrements it with CAS (Compare-And-Swap), and reaching zero releases all queued waiters at

once.
#mermaid-svg-7fSoKxQoq3zT7fPF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7fSoKxQoq3zT7fPF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7fSoKxQoq3zT7fPF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7fSoKxQoq3zT7fPF .error-icon{fill:#552222;}#mermaid-svg-7fSoKxQoq3zT7fPF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7fSoKxQoq3zT7fPF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7fSoKxQoq3zT7fPF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7fSoKxQoq3zT7fPF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7fSoKxQoq3zT7fPF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7fSoKxQoq3zT7fPF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7fSoKxQoq3zT7fPF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7fSoKxQoq3zT7fPF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7fSoKxQoq3zT7fPF .marker.cross{stroke:#333333;}#mermaid-svg-7fSoKxQoq3zT7fPF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7fSoKxQoq3zT7fPF p{margin:0;}#mermaid-svg-7fSoKxQoq3zT7fPF defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-7fSoKxQoq3zT7fPF g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-7fSoKxQoq3zT7fPF g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-7fSoKxQoq3zT7fPF g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-7fSoKxQoq3zT7fPF g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-7fSoKxQoq3zT7fPF g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-7fSoKxQoq3zT7fPF .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-7fSoKxQoq3zT7fPF .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-7fSoKxQoq3zT7fPF .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-7fSoKxQoq3zT7fPF .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-7fSoKxQoq3zT7fPF .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-7fSoKxQoq3zT7fPF .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-7fSoKxQoq3zT7fPF .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-7fSoKxQoq3zT7fPF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7fSoKxQoq3zT7fPF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7fSoKxQoq3zT7fPF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7fSoKxQoq3zT7fPF .edgeLabel .label text{fill:#333;}#mermaid-svg-7fSoKxQoq3zT7fPF .label div .edgeLabel{color:#333;}#mermaid-svg-7fSoKxQoq3zT7fPF .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-7fSoKxQoq3zT7fPF .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-7fSoKxQoq3zT7fPF .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-7fSoKxQoq3zT7fPF .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-7fSoKxQoq3zT7fPF .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-7fSoKxQoq3zT7fPF .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7fSoKxQoq3zT7fPF .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7fSoKxQoq3zT7fPF #statediagram-barbEnd{fill:#333333;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7fSoKxQoq3zT7fPF .cluster-label,#mermaid-svg-7fSoKxQoq3zT7fPF .nodeLabel{color:#131300;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-7fSoKxQoq3zT7fPF .note-edge{stroke-dasharray:5;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-note text{fill:black;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram-note .nodeLabel{color:black;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagram .edgeLabel{color:red;}#mermaid-svg-7fSoKxQoq3zT7fPF #dependencyStart,#mermaid-svg-7fSoKxQoq3zT7fPF #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-7fSoKxQoq3zT7fPF .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7fSoKxQoq3zT7fPF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} new CountDownLatch(n)
countDown() while count > 1
countDown() makes count 0
countDown() is a no-op
Counting
Open
await() blocks here
await() returns at once

Pattern 1 --- wait for N tasks to finish

The main thread creates a latch with the number of tasks, each worker counts down when it finishes, and the

main thread awaits.

java 复制代码
public class Worker implements Runnable {
    private final List<String> output;
    private final CountDownLatch done;

    Worker(List<String> output, CountDownLatch done) {
        this.output = output;
        this.done = done;
    }

    @Override
    public void run() {
        try {
            doSomeWork();
            output.add("Counted down");
        } finally {
            done.countDown();   // always, even if the work throws
        }
    }
}
java 复制代码
@Test
void whenParallelProcessing_thenMainThreadWillBlockUntilCompletion() throws InterruptedException {
    List<String> output = Collections.synchronizedList(new ArrayList<>());
    CountDownLatch done = new CountDownLatch(5);

    List<Thread> workers = Stream.generate(() -> new Thread(new Worker(output, done)))
            .limit(5)
            .toList();
    workers.forEach(Thread::start);

    done.await();                       // main thread blocks here
    output.add("Latch released");

    assertThat(output).containsExactly(
            "Counted down", "Counted down", "Counted down", "Counted down", "Counted down",
            "Latch released");
}

Pattern 2 --- start N threads at the same moment

Flip the roles: a latch of 1 acts as a starting gun. Every worker awaits it, and the main thread releases them

all at once. Combine it with a second latch to also wait for completion.
doneSignal (N) Workers x N startSignal (1) Main thread doneSignal (N) Workers x N startSignal (1) Main thread #mermaid-svg-y9SKW4xbPKUIXy3f{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-y9SKW4xbPKUIXy3f .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-y9SKW4xbPKUIXy3f .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-y9SKW4xbPKUIXy3f .error-icon{fill:#552222;}#mermaid-svg-y9SKW4xbPKUIXy3f .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-y9SKW4xbPKUIXy3f .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-y9SKW4xbPKUIXy3f .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-y9SKW4xbPKUIXy3f .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-y9SKW4xbPKUIXy3f .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-y9SKW4xbPKUIXy3f .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-y9SKW4xbPKUIXy3f .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-y9SKW4xbPKUIXy3f .marker{fill:#333333;stroke:#333333;}#mermaid-svg-y9SKW4xbPKUIXy3f .marker.cross{stroke:#333333;}#mermaid-svg-y9SKW4xbPKUIXy3f svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-y9SKW4xbPKUIXy3f p{margin:0;}#mermaid-svg-y9SKW4xbPKUIXy3f .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-y9SKW4xbPKUIXy3f text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-y9SKW4xbPKUIXy3f .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-y9SKW4xbPKUIXy3f .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-y9SKW4xbPKUIXy3f .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-y9SKW4xbPKUIXy3f .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-y9SKW4xbPKUIXy3f #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-y9SKW4xbPKUIXy3f .sequenceNumber{fill:white;}#mermaid-svg-y9SKW4xbPKUIXy3f #sequencenumber{fill:#333;}#mermaid-svg-y9SKW4xbPKUIXy3f #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-y9SKW4xbPKUIXy3f .messageText{fill:#333;stroke:none;}#mermaid-svg-y9SKW4xbPKUIXy3f .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-y9SKW4xbPKUIXy3f .labelText,#mermaid-svg-y9SKW4xbPKUIXy3f .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-y9SKW4xbPKUIXy3f .loopText,#mermaid-svg-y9SKW4xbPKUIXy3f .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-y9SKW4xbPKUIXy3f .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-y9SKW4xbPKUIXy3f .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-y9SKW4xbPKUIXy3f .noteText,#mermaid-svg-y9SKW4xbPKUIXy3f .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-y9SKW4xbPKUIXy3f .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-y9SKW4xbPKUIXy3f .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-y9SKW4xbPKUIXy3f .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-y9SKW4xbPKUIXy3f .actorPopupMenu{position:absolute;}#mermaid-svg-y9SKW4xbPKUIXy3f .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-y9SKW4xbPKUIXy3f .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-y9SKW4xbPKUIXy3f .actor-man circle,#mermaid-svg-y9SKW4xbPKUIXy3f line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-y9SKW4xbPKUIXy3f :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} all parked, ready start N threads await() countDown() released together do work concurrently countDown() each await() released when count = 0

java 复制代码
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal  = new CountDownLatch(N);

for (int i = 0; i < N; i++) {
    new Thread(() -> {
        try {
            startSignal.await();      // wait for the gun
            hitTheService();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            doneSignal.countDown();
        }
    }).start();
}

startSignal.countDown();              // everyone goes now
doneSignal.await();

This is the classic way to provoke race conditions in a test: without the start gun, threads start one by one and

rarely overlap.

Pattern 3 --- do not wait forever

If a worker dies before counting down, a plain await() blocks forever. Production code should use the timed

variant and decide what a timeout means.

java 复制代码
boolean completed = done.await(3, TimeUnit.SECONDS);
if (!completed) {
    throw new IllegalStateException("Timed out; " + done.getCount() + " task(s) still running");
}

A realistic use: parallel warm-up during Spring Boot startup

An ApplicationRunner runs after Tomcat has started but before the application reports ready (see

Spring Boot Startup Lifecycle). Warming caches there in

parallel, and failing startup if they don't finish in time, keeps a half-warm pod out of the load balancer.

java 复制代码
@Component
public class CacheWarmUpRunner implements ApplicationRunner {

    private final List<CacheLoader> loaders;

    public CacheWarmUpRunner(List<CacheLoader> loaders) {
        this.loaders = loaders;
    }

    @Override
    public void run(ApplicationArguments args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(loaders.size());
        try (ExecutorService pool = Executors.newVirtualThreadPerTaskExecutor()) {   // Java 21+
            for (CacheLoader loader : loaders) {
                pool.submit(() -> {
                    try {
                        loader.load();
                    } finally {
                        latch.countDown();
                    }
                });
            }
            if (!latch.await(30, TimeUnit.SECONDS)) {
                pool.shutdownNow();   // interrupt stragglers, otherwise close() would wait for them
                throw new IllegalStateException("Cache warm-up timed out, remaining=" + latch.getCount());
            }
        }
    }
}

ExecutorService.close() (Java 19+) waits for submitted tasks to finish, so the timeout path must interrupt them

first --- and the loaders must respond to interruption for the timeout to be real.

Note that the latch only reports completion , not success : a loader that throws still counts down. If failures

matter, collect them (e.g. in a ConcurrentLinkedQueue) or use Futures --- see the comparison below.

Pitfalls

Pitfall Consequence Fix
countDown() not in finally An exception leaves the count above 0; await() hangs Always count down in finally
Untimed await() in production A lost worker hangs the caller forever await(timeout, unit) and handle false
Count does not match the number of tasks Too high: hang. Too low: caller proceeds early Derive the count from the task list
Swallowing InterruptedException Cancellation is lost Restore with Thread.currentThread().interrupt() or propagate
Treating completion as success Failed tasks look done Collect errors or use Future/CompletableFuture
Trying to reuse the latch Second round never blocks Create a new latch, or use CyclicBarrier
Thread.sleep in tests instead of a latch Slow and flaky tests Count down from the async callback and await with a timeout

Choosing a tool

Tool Semantics Reusable Carries results/errors
CountDownLatch Wait until count reaches 0 No No
CyclicBarrier N parties wait for each other, then all proceed Yes (resets) No
Semaphore At most N concurrent permits Yes No
Phaser Like a barrier with dynamic party registration and phases Yes No
ExecutorService.invokeAll Submit and wait for a batch of Callables n/a Yes (Future)
CompletableFuture.allOf Compose async results n/a Yes

Rule of thumb: use CountDownLatch when you need a simple "wait for these N events" signal that crosses thread

boundaries you do not control (callbacks, listeners, test hooks). When you own the tasks, invokeAll or

CompletableFuture.allOf also give you their results and exceptions.

Related notes:

  • The same permit concept in Python: threading.Semaphore.
  • For reactive code, compose publishers (Flux.merge(...).then()) instead of blocking:
    Mono vs Flux.

Conclusion

CountDownLatch is the smallest useful coordination primitive in JUC: a one-shot, ownerless countdown that

releases all waiters at zero. Two patterns cover almost every use --- wait for N to finish and start N at once ---

and three habits make it safe: count down in finally, await with a timeout, and never mistake completion for

success.

References

相关推荐
代码什么用7 小时前
Spring基础使用
java·后端·spring
Su米苏7 小时前
Spring AI 中MCP 与普通 @Tool的区别
java·人工智能·spring
码云数智-园园7 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
老板一杯拿铁7 小时前
Java 异常体系详解:从字节码原理到最佳实践
java
蜗牛互联网8 小时前
Java 17 HttpClient调用文件转写API的超时与失败回退
java·人工智能·后端
禾小西8 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis
我不是阵雨8 小时前
JDK 21虚拟线程Pinning陷阱:一文拔钉解困
java·开发语言
小卡车5559 小时前
java反射、自定义注解的应用(自定义分页)
java
BLUcoding9 小时前
接口服务公网超时排查记录:SecureRandom 阻塞问题分析与解决
java·linux·springboot·aes·securerandom
yuniko-n10 小时前
【JUC】Lock 和 synchronzied 锁
java