CountDownLatch 是基于递减计数器的同步工具,用于主线程等待多个子线程完成;初始化指定计数,countDown() 减1,await() 阻塞至归零或超时,不可重置,需防漏调、误调及无超时风险。

CountDownLatch 是 Java 并发包中轻量、易用的同步工具,适合“主线程等待多个子线程全部完成后再继续执行”的典型场景。它不涉及线程阻塞唤醒的复杂调度,核心是基于一个递减计数器,用完即开闸,逻辑清晰,不易出错。
理解 CountDownLatch 的基本机制
CountDownLatch 初始化时指定一个正整数 count,表示需等待的事件数量(通常对应子线程数)。每次调用 countDown() 方法,计数器减 1;主线程调用 await() 会阻塞,直到计数器归零或被中断。
- 计数器只能递减,不可重置,用完即失效(如需重复使用,应考虑 CyclicBarrier)
-
await()支持超时控制,避免无限等待,建议生产环境始终设置合理超时时间 -
countDown()可在任意线程中安全调用,不限于工作线程本身(例如可在异步回调、CompletableFuture.thenRun 中触发)
典型使用步骤与代码结构
以启动 3 个子任务并统一等待为例:
- 创建 CountDownLatch 实例:
CountDownLatch latch = new CountDownLatch(3); - 每个子线程执行完核心逻辑后,必须调用
latch.countDown();(建议放在 finally 块中,防止异常跳过) - 主线程在启动所有子线程后,调用
latch.await(10, TimeUnit.SECONDS);等待或超时 - 检查返回值:若
await()返回 true 表示计数归零成功;返回 false 表示超时,此时可主动处理未完成任务(如取消、记录告警)
常见陷阱与规避建议
看似简单,但实际使用中容易忽略关键细节:
- 忘记调用
countDown()—— 导致主线程永久阻塞。务必确保每个执行路径(包括异常分支)都覆盖该调用 - 重复调用
countDown()—— 虽然线程安全,但可能使计数器提前归零,造成误判。建议每个子任务只调用一次,且由任务自身负责 - 忽略 await 超时 —— 在网络请求、IO 等不确定耗时场景下,不设超时等于埋雷。可配合 try-catch 处理 InterruptedException
- 混淆 CountDownLatch 与 join() ——
join()是针对单个 Thread 对象的阻塞,无法优雅管理多个异构任务;而 CountDownLatch 解耦更干净,适用于 Runnable/Callable/线程池等任意执行单元
结合线程池的实用写法
实际项目多用线程池提交任务,此时需注意:任务提交即返回,不能依赖 submit() 后立即 countDown()。正确做法是在任务内部完成时调用:
ExecutorService pool = Executors.newFixedThreadPool(3);
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
pool.submit(() -> {
try {
// 执行业务逻辑(如 HTTP 请求、文件处理)
doWork();
} finally {
latch.countDown(); // 确保无论成功失败都倒计时
}
});
}
// 主线程等待
if (!latch.await(15, TimeUnit.SECONDS)) {
System.err.println("部分任务超时未完成");
}
pool.shutdown();
不复杂但容易忽略。关键是把 countDown 当作任务的“完成信标”,而不是启动信号;把 await 当作协调点,而非魔法开关。

















