说说 CountDownLatch 与 CyclicBarrier 区别
1.CountDownLatch 的作用是允许 1 或 N 个线程等待其他线程完成执行;而 CyclicBarrier 则是允许 N 个线程相互等待。
2.CountDownLatch 的计数器无法被重置; CyclicBarrier 的计数器可以被重置后使用,因此它被称为是循环的 barrier。
两条核心区别准确,但只回答了”是什么”。要能在设计中选对工具,还需要:两者的执行模型差异(谁等谁、何时放行)、barrierAction 的独特价值、以及各自失效场景。本文用模型图景 + 代码对照补全。
执行模型对照
CountDownLatch(门闩):计数器从 N 开始,countDown() 减一(通常由工作线程调用),await() 阻塞等待归零(通常由协调线程调用)。方向是”多对一/多对多”的事件等待:等待方关心的是”N 件事都做完了”,不关心是谁做的。
CountDownLatch latch = new CountDownLatch(3); |
CyclicBarrier(栅栏):N 个线程各自执行到 await() 处互相等待,凑齐 N 个后全体同时放行,并可先执行一个 barrierAction。方向是”点对点集合”:每个参与者既是等待者也是被等者。
CyclicBarrier barrier = new CyclicBarrier(3, () -> log.info("本轮三家报价齐")); |
差异全景
| 维度 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 等待模型 | 等待方等事件完成(可 1 等 N) | N 个参与者互相等待到齐 |
| 复用 | 一次性(归零即废) | 可循环复用(每轮凑齐后重置) |
| 集合动作 | 无 | barrierAction(到齐时执行一次) |
| 计数方向 | 只减 | 每轮到齐后自动重置回 N |
| 实现 | AQS 共享模式 | ReentrantLock + Condition |
| 失效场景 | 计数不足 → 永久等待 | 某参与者异常/超时 → 屏障破损(BrokenBarrierException),全体释放 |
复用性与”循环”的含义
CyclicBarrier 的 Cyclic 指多轮同步:分布式计算中”迭代-同步-再迭代”(如每轮 map 后全体 barrier 再进入 reduce)、多线程分阶段压测(每轮开始对齐发令),都是它的天然场景。CountDownLatch 要重复使用只能每次新建实例。
反过来,一次性启动协调(等 N 个服务初始化完成再开放流量)用 CountDownLatch 语义更直白,且允许”等待方与计数方数量不同”(如 1 个主线程等 10 个初始化任务),这是 CyclicBarrier 做不到的(它的 await 者必须计入 N)。
失效与健壮性
- CountDownLatch 最怕计数泄漏:任务异常跳过 countDown,await 方死等。规范写法是 countDown 放 finally,await 用超时版兜底
- CyclicBarrier 的破损语义更完整:任一参与者 await 超时或中断,屏障 broken,其余等待者立即收到 BrokenBarrierException 而非陪葬死等;
reset()可重建屏障开始新一轮 - 两者都不处理”结果传递”:需要汇聚返回值时用 CompletableFuture.allOf 或 CompletionService 更合适
选型口诀
- 等事完成(异步转同步、启动协调) → CountDownLatch
- 等人到齐(多轮阶段同步、并行迭代对齐) → CyclicBarrier
- 要结果汇聚与编排 → CompletableFuture
一句话总结模型差异:CountDownLatch 是”裁判等选手冲线”,CyclicBarrier 是”选手们在检查点互相等齐再一起出发”。抓住等待方向与复用性这两点,其余差异(barrierAction、破损语义)都是模型的自然推论。











