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

相关推荐
Fuyo_11194 分钟前
C++ AVL树保姆级详解:从平衡因子到四大旋转机制
java·c++·算法
Sarvartha22 分钟前
Date、LocalDate、LocalTime、LocalDateTime、DateTimeFormatter类
java
阿俊-全栈开发39 分钟前
LikeShop 多商户 Java 版商城微服务模块拆分与部署架构演进实践
java·微服务·架构·likeshop开源商城·多商户源码
Wang's Blog1 小时前
Java 项目实战:交友项目 - 联系人列表
java·开发语言·交友
我命由我123451 小时前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理
香蕉大香蕉(06敬畏市场)2 小时前
MySQL 单点详解(01)
java·数据库·mysql·面试·博客·实习
LaughingZhu2 小时前
Product Hunt 热榜 2026-10-30:Empacer、Scape、Floot in ChatGPT
java·前端·数据库·人工智能·chatgpt·notion
niucloud-admin2 小时前
NIUCLOUD PHP SAAS上门家政使用文档:服务保障
java·开发语言·php
IT枫斗者枫哥3 小时前
CompletableFuture失败后返回空列表:异常没了,业务含义也变了
java·后端
知识搬运临时工3 小时前
一个模板把 AEM 拖进 Full GC 死亡螺旋:300% CPU、7 GB 堆、310 万行堆栈
java·aws