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);
// 线程 A、B 先到达:count 3→2→1,各自挂起在 trip 上
// 线程 C 最后到达:count 1→0,C 触发放行

到齐时刻:dowait 的三步

最后一个到达的线程在 dowait() 中依次做:

  1. 执行 barrierAction(若构造时传入):在锁保护下由最后到达者运行一次,适合做”本轮汇总”(如合并各线程的局部结果)
  2. 代际翻转(nextGeneration):generation++、count 重置回 parties、trip.signalAll() 唤醒全部等待线程
  3. 等待者被唤醒后检查 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 行为皆可推导。