CountDownLatch 不直接解耦,而是通过倒计数器和 await()/countDown() 实现线程间时序协调;初始化指定计数值,await() 阻塞等待归零,countDown() 减一并可能唤醒等待线程,计数器归零后不可重置,需在 finally 中调用 countDown() 避免阻塞,常与 ExecutorService 或 CompletableFuture 配合使用。

CountDownLatch 本身不直接实现“解耦”,它提供的是同步等待机制,主子线程通过共享一个 CountDownLatch 实例来协调执行时序,从而在逻辑上分离职责——主线程不关心子线程具体做什么,只等待其完成;子线程也不依赖主线程调度,只需在结束时调用 countDown()。真正的解耦靠的是设计(如任务封装、接口抽象),CountDownLatch 是支撑这种协作的轻量级同步工具。
用法核心:一个倒计数器 + 两个关键操作
CountDownLatch 初始化时指定计数值(比如 3),代表需等待的事件数量。它有两个不可逆的核心操作:
- await():调用线程阻塞,直到计数器归零或被中断
- countDown():计数器减 1,可能唤醒正在 await 的线程
注意:计数器一旦归零,后续所有 await() 立即返回,countDown() 也继续生效但无实际效果——它是一次性门闩(latch),不可重置。
典型协作模式:主线程发令,子线程执行并汇报
主线程负责启动多个子任务,并统一等待全部完成;子线程各自独立运行,完成后通知“我好了”。例如启动 3 个异步数据加载任务:
立即学习“Java免费学习笔记(深入)”;
CountDownLatch latch = new CountDownLatch(3);
// 启动子线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 模拟耗时操作
loadData();
} finally {
latch.countDown(); // 完成后倒计数减一
}
}).start();
}
// 主线程等待全部完成
try {
latch.await(); // 阻塞直到 3 次 countDown 执行完毕
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("所有子任务已完成");
避免常见陷阱:别让 countDown 被跳过或重复遗漏
如果某个子线程因异常提前退出,且未执行 countDown,主线程将永久阻塞。因此务必把 countDown 放在 finally 块中。另外,不要在同一个线程里多次调用 countDown(除非业务确实需要多次信号),否则会提前打开门闩,导致 await 提前返回。
- ✅ 正确:每个子线程保证一次 countDown(哪怕出异常)
- ❌ 错误:把 countDown 放在 try 块内、没加 finally
- ❌ 错误:主线程自己也调用了 countDown,打乱预期计数
进阶提示:与 CompletableFuture 或 ExecutorService 搭配更自然
纯裸写 Thread + CountDownLatch 易出错。实际项目中更推荐组合使用:
- 用 ExecutorService 统一管理线程,submit 任务后用 CountDownLatch 等待结果汇总
- 用 CompletableFuture.allOf() 替代 CountDownLatch,语义更清晰(尤其适合有返回值的场景)
- 若需超时控制,
await(long timeout, TimeUnit unit)比无参版本更安全
CountDownLatch 的价值在于简单、低开销、语义明确——它不传递数据、不处理异常、不管理线程生命周期,只专注“等几个动作做完”。用好它,主子线程就能各司其职,协作不耦合。


















