说说 CyclicBarrier 原理
CyclicBarrier 是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点(CommonBarrierPoint)。因为该 barrier 在释放等待线程后可以重用,所以称它为循环的 barrier。
定义一句话讲完,原理要回答三个问题:到齐之前线程停在哪(等待机制)、到齐那一刻发生了什么(放行与 barrierAction)、为什么能循环(generation 代际)。本文按这三问展开,并补上破损语义与实战场景。
等待机制:ReentrantLock + Condition
与 CountDownLatch 基于 AQS 共享模式不同,CyclicBarrier 内部用一把 ReentrantLock 加一个 Condition(trip) 实现:
- 每个线程调用
await()时先持锁,把计数器count减一 - count 不为 0:当前线程在 trip 条件上
await()释放锁并挂起,等待被唤醒 - count 减到 0:**当前线程就是”最后一个到齐者”**,由它执行屏障动作并唤醒所有人
CyclicBarrier barrier = new CyclicBarrier(3); |
到齐时刻:dowait 的三步
最后一个到达的线程在 dowait() 中依次做:
- 执行 barrierAction(若构造时传入):在锁保护下由最后到达者运行一次,适合做”本轮汇总”(如合并各线程的局部结果)
- 代际翻转(nextGeneration):
generation++、count 重置回 parties、trip.signalAll()唤醒全部等待线程 - 等待者被唤醒后检查 generation 已变化,确认本轮屏障完成,各自返回继续执行
generation(代)是”循环”与”破损”两个语义的共同载体:正常翻代代表一轮完成;broken 翻代代表一轮作废。
循环重用与 reset
- 每轮放行后 count 自动重置,屏障对象可直接用于下一轮——无需新建,这是与 CountDownLatch 一次性语义的根本区别
reset()主动作废当前轮:正在等待的线程收到 BrokenBarrierException,generation 翻转、count 重置,屏障进入新一轮。适合”超时放弃本轮、重新开始”的策略
破损语义:一个人出问题,全体快速失败
任一参与者出现以下情况,屏障立即 broken:
- await 超时(带超时版本)
- 等待中被中断
- 屏障被 reset
- barrierAction 执行抛异常
broken 后所有等待者与后续到达者都抛 BrokenBarrierException——快速失败避免其余线程无意义陪等。恢复方式是 reset 或新建屏障。对比 CountDownLatch 的”计数泄漏即永久死等”,CyclicBarrier 的失败传播设计更健壮。
典型场景
- 多轮并行计算同步:矩阵分块迭代、MapReduce 式”局部计算-全局合并-再迭代”,每轮边界用 barrier 对齐
- 分阶段压测/对账:N 个线程各自准备数据,barrier 对齐后同时发压(比 sleep 等待精确)
- 多源数据凑齐再处理:如三家报价查询齐了再比价(barrierAction 里做汇总)
与 CountDownLatch / Semaphore 的定位
- CountDownLatch:等”事件完成”,一次性,等待方与计数方可不等量
- CyclicBarrier:等”参与者到齐”,可循环,参与者即等待者
- Semaphore:限”并发许可数”,不要求到齐,控制同时进行的数量
CyclicBarrier 的原理核心就两个对象:一把锁保护计数,一个条件队列停放等待者;而 generation 代际机制同时撑起了”循环”与”破损”两种生命周期语义。抓住这三点,API 行为皆可推导。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 非鱼小站!
评论
WalineDisqus











