countdownlatch是主线程等待所有子线程执行完毕的最常用轻量方式,通过原子计数器实现一次性等待:初始化指定计数,子线程调用countdown()递减,主线程await()阻塞至归零,不可重置,需配合try-finally防漏减。

主线程等待所有子线程执行完毕,最常用且轻量的方式就是 CountDownLatch。它不涉及线程阻塞唤醒的复杂调度,只靠一个原子计数器控制等待与释放,适合“一次性等待多个任务结束”的场景。
CountDownLatch 的核心逻辑
CountDownLatch 初始化时指定一个正整数(如 count = 5),表示需要等待的事件数量。每次调用 countDown(),计数器减一;主线程调用 await() 会阻塞,直到计数器归零或超时。
- 计数器只能递减,不能重置(需新建实例)
- await() 可设超时时间,避免无限等待
- 子线程只需在完成时调用 countDown(),无需关心主线程状态
典型使用步骤(代码结构)
以启动 3 个子线程处理数据、主线程汇总结果为例:
- 创建 CountDownLatch latch = new CountDownLatch(3)
- 每个子线程在 run() 或 execute() 最后调用 latch.countDown()
- 主线程在启动全部子线程后,立即调用 latch.await()
- await 返回后,可安全读取共享结果(建议用线程安全容器或加锁)
注意事项与常见坑
看似简单,但实际容易出错:
- 子线程未执行 countDown()(比如异常提前退出)→ 主线程永久阻塞 → 建议配合 try-finally 使用:try { ... } finally { latch.countDown(); }
- 主线程调用 await() 前就启动了部分子线程 → 无影响,但要确保所有子线程都已 start/submit,否则漏减计数
- 误在主线程多次调用 countDown() → 计数器可能提前归零,导致 await 提前返回 → 只让子线程调用
- 需要重复等待?CountDownLatch 不支持重用 → 改用 CyclicBarrier 或重新 new 实例
对比其他等待方式
不是所有场景都适合 CountDownLatch:
- join():简单但只能等单个线程,且耦合性强(必须持有 Thread 引用)
- CompletableFuture.allOf():更函数式,支持链式回调和异常传播,适合异步编排
- ExecutorService.invokeAll():适合提交 Callable 任务并统一获取 Future 列表
CountDownLatch 的优势在于低侵入、易理解、适用于任意线程模型(Thread / Runnable / 线程池任务 / even 非 Java 线程通过 JNI 回调)。











