CountDownLatch不可重复使用因其state减至0后无重置逻辑,而CyclicBarrier通过generation和count双状态实现可重用;前者适用于一次性同步,后者适合多轮协作。

CountDownLatch 为什么不能重复使用
它内部靠一个 state 计数器控制,每次调用 countDown() 就减一,减到 0 后所有等待线程被唤醒,但 state 永远卡在 0 —— 没有重置逻辑。一旦触发,就彻底失效。
常见错误现象:await() 在第二次调用时直接返回(因为 state == 0),看起来“没阻塞”,但实际是已经“过期”了,不是你预期的再次等待。
- 适用场景:启动信号(如主线程等所有子线程初始化完成)、服务关闭等待、一次性屏障
- 别试图通过新建对象来“模拟重用”——高并发下频繁 new
CountDownLatch会增加 GC 压力 - 注意
countDown()调用次数必须严格等于构造时传入的计数值,少调用会导致await()永久阻塞
CyclicBarrier 怎么做到可重用
它底层用 generation + count 双状态管理:每次所有线程到达后,count 重置为初始值,generation 翻转(类似版本号),等待线程自动进入下一轮。
关键点在于:await() 返回时,该轮次已结束;下次再调用 await(),就是新轮次的开始。
立即学习“Java免费学习笔记(深入)”;
- 适用场景:多阶段并行计算(如每轮迭代都需同步)、分批处理中的批次间协调
- 可选传入
Runnable参数,在每轮最后一名线程到达时执行一次(注意:仅由最后一个到达者执行,不是每个线程都执行) - 如果某线程在
await()时被中断,整个栅栏会被打破(broken状态),后续所有await()都抛BrokenBarrierException
两者在异常处理和中断响应上的差异
CountDownLatch.await() 被中断时抛 InterruptedException,但不会影响其他等待线程,也不会破坏 latch 本身状态;而 CyclicBarrier.await() 被中断或超时,会直接把栅栏置为 broken,后续调用全失败。
-
CountDownLatch更“温和”:单个线程出问题,不影响整体协作流程继续 -
CyclicBarrier更“严格”:强调全员参与,一人掉队,整轮作废,必须显式调用reset()(不推荐,有竞态)或重建实例 - 若需容忍个别线程失败,优先选
CountDownLatch;若必须强一致性同步,选CyclicBarrier
性能与内存开销的实际差别
两者底层都基于 AQS,单次等待/释放的开销接近,但长期运行中差异来自设计意图:
-
CountDownLatch是一次性状态机,无额外维护成本 -
CyclicBarrier每轮都要更新generation、重置count、清理等待队列,有微量额外操作 - 真正影响性能的是误用:比如用
CyclicBarrier实现单次启动信号,反而引入不必要的重入检查和版本管理
真正容易被忽略的是:它们都不是“锁”,不保证临界区互斥;需要配合 synchronized 或 ReentrantLock 使用才能保护共享数据。光靠 await 同步,不代表线程安全。









