countdownlatch是java并发包中用于线程同步的轻量级辅助类,作用是让一个或多个线程等待其他线程完成指定数量的操作后再继续执行;适用于主线程等待多个子任务完成、初始化依赖异步加载等场景,不可重用,需避免计数设置错误和countdown调用位置不当。

CountDownLatch 是什么,什么时候该用它CountDownLatch 是 Java 并发包里一个轻量级的同步辅助类,核心就干一件事:让一个或多个线程等待,直到其他线程完成一组操作。它不涉及锁竞争,也不可重用,计数器归零后就永远保持在 0 状态。
适合场景包括:
- 主线程启动多个子任务(比如批量调用下游接口、并行计算分片),需要等全部完成再汇总结果
- 测试中模拟“所有线程就绪后同时开始”的栅栏行为(注意:这不是它的本职,
CyclicBarrier更合适) - 初始化依赖多个异步加载项,必须等齐了才能对外提供服务
别误用它来替代 join()(线程生命周期绑定太紧)、也别指望它能唤醒特定线程——它只提供“阻塞+倒数”两个原子操作。
初始化和倒数的常见错误写法
最常踩的坑是计数值设错,或者倒数位置不对:
- 初始化时传入 0:
new CountDownLatch(0) —— 这会导致 await() 立即返回,看似“成功”,实则掩盖了逻辑缺陷
- 在非工作线程里调用
countDown(),比如主线程自己减一,而子线程没减——结果就是 await() 永远卡住
- 多次调用
countDown() 超过初始值,虽然不会报错,但多余调用无效,容易误导排查
new CountDownLatch(0) —— 这会导致 await() 立即返回,看似“成功”,实则掩盖了逻辑缺陷countDown(),比如主线程自己减一,而子线程没减——结果就是 await() 永远卡住countDown() 超过初始值,虽然不会报错,但多余调用无效,容易误导排查正确姿势:
int taskCount = 5;
CountDownLatch latch = new CountDownLatch(taskCount);
<p>for (int i = 0; i {
try {
// 执行具体任务
doWork();
} finally {
latch.countDown(); // 必须放在 finally,确保异常时也能倒数
}
}).start();
}</p><p>latch.await(); // 主线程阻塞至此,直到 count 变为 0</p>
await() 的超时控制和中断响应await() 有两个重载版本,不用超时参数的会一直等下去,生产环境几乎从不推荐:
-
await():无超时,线程可能永久挂起(比如某个子线程崩溃未执行 countDown())
-
await(long timeout, TimeUnit unit):带超时,返回 boolean 表示是否在超时前完成
await():无超时,线程可能永久挂起(比如某个子线程崩溃未执行 countDown())await(long timeout, TimeUnit unit):带超时,返回 boolean 表示是否在超时前完成建议始终用带超时的版本,并根据业务容忍度设置合理阈值:
- 批量 HTTP 请求:可设为单次请求超时 × 1.5 倍,再加固定缓冲(如 3s)
- 内存计算任务:通常 1–5 秒足够,超时大概率说明有死循环或资源争用
另外,await() 可被中断,如果当前线程收到 interrupt(),会抛出 InterruptedException 并清除中断状态。记得捕获后做清理或重新设置中断标志:
try {
if (!latch.await(10, TimeUnit.SECONDS)) {
throw new RuntimeException("Tasks timed out");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
throw new RuntimeException(e);
}
与 CompletableFuture 的协作边界在哪
有人会问:“现在都用 CompletableFuture 了,还要 CountDownLatch 吗?”答案是:要看场景。
- 如果你只是等几个异步任务结束,且不需要链式处理、异常传播、组合逻辑,
CountDownLatch 更轻、更直观、无额外对象开销
- 如果你需要对每个子任务的结果做映射、过滤、异常分类处理,或者要和 Spring 的异步支持集成,
CompletableFuture.allOf() 是更自然的选择
CountDownLatch 更轻、更直观、无额外对象开销CompletableFuture.allOf() 是更自然的选择但注意:allOf() 不返回结果集合,要手动 collect;而 CountDownLatch 根本不关心结果,只管“有没有做完”。两者不是替代关系,而是职责分离。
真正容易被忽略的是:CountDownLatch 不感知子线程是否真的执行成功,只看有没有调用 countDown()。所以务必把 countDown() 放在 finally 块里,哪怕任务抛异常也要倒数——否则汇聚点永远等不到。










