CountDownLatch 的 countDown() 必须在任务真正完成后调用,否则会导致 await() 无限阻塞;常见错误包括启动线程后立即调用、异常未捕获致 countDown 被跳过、多分支遗漏等;应使用 try-finally 确保执行,并配合超时 await、count 检查和线程 dump 定位问题;优先考虑 CompletableFuture 等更健壮的替代方案。

CountDownLatch 的 countDown() 调用位置错误,确实会导致主线程(或等待线程)在 await() 处无限阻塞——表面看是“子线程卡死”,实际是等待方被挂起,而倒计时永远无法归零。
countDown 必须在任务真正完成之后调用
常见误写是在启动子线程后立即调用 countDown(),而非等子线程逻辑执行完毕。此时倒计时提前减为 0,看似正常;但更危险的是反向错误:把 countDown() 写在子线程内部,却因异常、return 或逻辑遗漏未执行。
- 错误示例:
new Thread(() -> { doWork(); latch.countDown(); }).start();—— 看似合理,但如果doWork()抛异常且未捕获,countDown()就不会执行 - 正确做法:用 try-finally 包裹,确保无论是否异常都触发倒计时
子线程中漏写 countDown 是最隐蔽的卡死原因
尤其在多分支、条件提前返回、或嵌套异步调用场景下,countDown() 容易被遗忘或只写在某条路径里。
- 检查所有可能退出子线程的路径:正常 return、异常 throw、break 循环、甚至 System.exit() 前是否都覆盖了 countDown
- 若子线程内含回调(如网络请求 completion handler),需确认回调里调用了 countDown,而非在发起请求后立刻调用
await 超时不是万能解,但能暴露问题
用 latch.await(10, TimeUnit.SECONDS) 替代无参 await(),能让卡死变成可观察的超时异常,便于定位哪路子线程没完成。
立即学习“Java免费学习笔记(深入)”;
- 超时后打印当前 count 值:
System.out.println("Remaining: " + latch.getCount());,非零即说明有线程未调用 countDown - 结合线程 dump(jstack)查看哪些线程还阻塞在 await 上,再反查对应子线程的执行状态
用 CountDownLatch 时优先考虑替代方案
CountDownLatch 本身不可重用,且依赖人工维护调用时机,出错成本高。现代代码中可考虑:
- CompletableFuture:链式编排 + exceptionally 处理,天然保证完成通知
- ExecutorService.invokeAll():批量提交 Callable,自动聚合结果并感知全部完成
- Phaser 或 CyclicBarrier:需要重复同步或多方协作时更合适


















