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

相关推荐
SL_staff1 小时前
从RBAC到场景化授权:《无忧·企业文档》三级权限模型的技术实践解析
java·开源·产品
传奇开心果编程2 小时前
【springboot基础语法学与练】第 1 课:从零开始
java·spring boot·后端·学习
SL_staff2 小时前
财务系统慎用低代码?从数据模型闭环看合规落地的技术实践
java·低代码·全栈
滕州市燕猫虎计算机科技工作室个体工商户2 小时前
IDEA:Command line is too long
java·ide·intellij-idea
步行cgn3 小时前
Spring p 命名空间注入详解
java·前端·spring
YatHinLay3 小时前
MyBatis 流式查询实战:ResultHandler 处理海量数据
java
天天被压力3 小时前
【跨市场数据实战 #08】可转债折价机会怎么筛:3个接口抓比价、列表和实时盘口
java·人工智能·python
张某布响丸辣4 小时前
附件下载的安全边界:路径白名单、NAS 哨兵文件与 Redis Lua 一次性令牌
java·redis·redis lua·nas哨兵
YatHinLay4 小时前
Spring Boot + Calcite 实现跨库查询
java