countdownlatch 初始化等待的典型用法是主线程等待所有子任务完成:构造时设计数值为子任务数,每个子任务在finally中调用countdown(),主线程启动全部子任务后立即带超时调用await()。

CountDownLatch 初始化等待的典型用法
主线程必须等所有子任务(比如数据库连接、配置加载、缓存预热)全部完成才能继续,CountDownLatch 是最轻量、最直接的选择。它不涉及线程池管理或回调链路,只解决“计数归零才放行”这一个明确问题。
关键不是“怎么创建”,而是“谁负责 countDown()”和“谁调用 await()”。漏掉一次 countDown() 就会永久阻塞;在错误线程里调用 await() 会导致主线程没等就往下走了。
- 初始化前构造
CountDownLatch,计数值 = 子任务数量 - 每个子任务执行完(无论成功失败)都必须调用一次
countDown() - 主线程在启动所有子任务后,立即调用
await()—— 不要放在子任务启动前,也不要漏掉
子任务异常时 countDown() 容易被跳过
常见错误是把 countDown() 写在 try 块里,结果子任务抛异常后直接跳到 catch,countDown() 没执行,主线程卡死。
正确做法是确保 countDown() 在 finally 块中,或者用 try-with-resources 风格包裹逻辑。哪怕子任务失败,也要“占一个坑位”,否则计数永远不归零。
executor.submit(() -> {
try {
initDatabase();
} catch (Exception e) {
log.error("DB init failed", e);
} finally {
latch.countDown(); // 必须放这里
}
});
await() 要加超时,避免无限等待
生产环境绝不允许无超时的 await()。某个子任务因网络、死锁或 bug 卡住,主线程就会永远 hang 住,整个应用无法启动。
超时时间建议设为所有子任务预期耗时总和的 1.5–2 倍,并配合日志和监控。超时后应主动中断未完成任务(如果可中断),并抛出初始化失败异常。
- 用
latch.await(30, TimeUnit.SECONDS)替代latch.await() - 返回
false表示超时,此时可检查latch.getCount()知道还剩几个没完成 - 不要忽略返回值:if (!latch.await(30, SECONDS)) { throw new InitException("Timeout"); }
和 CompletableFuture 比较:什么情况不该用 CountDownLatch
如果子任务之间有依赖(B 要等 A 结果)、需要组合返回值、或要统一处理异常堆栈,CountDownLatch 就力不从心了。它只管“是否完成”,不管“结果如何”。
它适合纯信号同步场景:你只关心“都干完了没”,不关心谁快谁慢、谁成功谁失败、要不要汇总结果。一旦出现以下任一需求,就该换 CompletableFuture.allOf():
- 需要获取每个子任务的返回值(比如加载的配置对象)
- 想对任意子任务失败做统一 fallback
- 后续流程要基于子任务结果做分支判断
多一个抽象层换来的是可维护性,但代价是代码略重、异常传播路径变长。简单初始化,CountDownLatch 仍是最快上手、最不容易出错的选择。










