CountDownLatch 是一次性同步屏障,用于等待多个事件完成而非保护共享资源;构造参数应为需调用 countDown() 的总次数,须在 finally 中调用以避免漏调导致永久阻塞。

CountDownLatch 适合等待多个线程完成后再继续执行的场景
它不是用来保护共享资源的,而是做「同步屏障」——主线程卡在 await(),直到其他线程调用足够次数的 countDown() 才放行。典型用途是:启动服务时等待所有初始化任务结束、测试中模拟并发请求后统一收尾、批处理中等子任务全部完成再汇总结果。
别把它当信号量或锁用
CountDownLatch 是一次性使用的,计数器归零后无法重置(没有 reset() 方法)。如果需要反复同步,该换 CyclicBarrier;如果要控制并发数,该用 Semaphore;如果要互斥访问变量,该上 synchronized 或 ReentrantLock。
- 误用示例:在循环里反复 new 一个
CountDownLatch(1)来模拟“每次只放行一个线程”——这本质是串行,且对象创建开销不必要 - 正确做法:这种需求直接用
ExecutorService.invokeAll()或配合Future更自然 - 注意
await(long, TimeUnit)的超时判断:返回false表示没等到就超时了,得自己处理失败路径,不能默认后续逻辑一定执行
构造参数设多少,取决于你真正要等的“事件数”
不是线程数,而是 countDown() 被调用的总次数。比如 3 个线程各自做初始化,每个线程做完都调一次 countDown(),那构造时传 3;但如果某个线程要做 2 件事、分两次通知,就得传 4 并确保调用 4 次。
- 漏调
countDown()→ 主线程永久阻塞(尤其在异常分支里忘记调用) - 多调 → 计数器变负,
await()立即返回,逻辑可能提前触发 - 建议在
finally块里写countDown(),保证无论是否异常都计数
和 CompletableFuture 配合更灵活,但别盲目替换
Java 8+ 后,很多原来用 CountDownLatch 的地方可以用 CompletableFuture.allOf() 替代,好处是支持链式回调、异常传播、组合依赖。但它引入了异步抽象,调试难度上升,且对简单等待场景属于过度设计。
立即学习“Java免费学习笔记(深入)”;
- 适合用
CompletableFuture:需要后续串行处理、要捕获各子任务异常、有依赖关系(A 完成后才触发 B) - 仍该用
CountDownLatch:纯阻塞等待、代码已稳定、不想引入额外回调层级、运行在受限环境(如某些 Android API 级别不完全支持 CF) - 别混用:比如一边用
latch.await(),一边又在子任务里用complete(),语义混乱且难维护
countDown() 缺失,以及把“等待 N 个线程”错误等同于“构造参数为 N”,而忽略了线程内多次通知的可能性。









