CyclicBarrier是 Java 并发包中一个可重用的同步辅助工具,用于让一组固定数量的线程互相等待,直到所有线程都到达某个“屏障点”(barrier point),然后一起继续执行。它的名字中的“Cyclic”(循环)正是因为它可以在一次使用后重置并重复使用。
下面从核心思想、内部机制、关键特性、与 CountDownLatch 的区别、内存语义五个方面帮你彻底理解。
一、核心思想:多线程“齐步走”
想象一个班级做操:
- 老师喊:“所有人到操场集合!”
- 学生 A、B、C……陆续到达,但必须等最后一个同学到齐,才能开始做操。
- 做完一轮后,老师又喊:“再来一轮!”,大家再次集合 →这就是“循环”。
在程序中:
- 每个线程执行一部分工作;
- 然后调用
barrier.await()等待; - 当所有 N 个线程都调用了
await(),屏障“放行”,所有线程继续; - 下次还能再用同一个
CyclicBarrier。
✅典型场景:并行迭代计算(如 Jacobi 迭代)、多阶段任务同步。
二、内部实现机制
1. 核心组件
privatefinalReentrantLocklock=newReentrantLock();privatefinalConditiontrip=lock.newCondition();// 等待/唤醒的条件privatefinalintparties;// 总线程数(固定)privateintcount;// 当前轮次剩余未到达的线程数privateGenerationgeneration;// 表示当前“代”(一轮)privatefinalRunnablebarrierCommand;// 屏障动作(可选)2.Generation:区分“轮次”
- 每次屏障触发(tripped)或重置(reset),就创建一个新的
Generation对象。 - 所有等待的线程都关联到同一个 generation。
- 如果某轮被中断(如超时、异常),该 generation 被标记为
broken = true。 - 新一轮开始时,
generation指向新对象,旧的被废弃。
💡 作用:防止“跨轮次”的线程互相干扰(比如上一轮的线程误唤醒下一轮的)。
3.dowait():核心等待逻辑
intindex=--count;// 倒计数if(index==0){// 最后一个线程到达!执行 barrierCommand(如果有);nextGeneration();// 重置 count,新建 generation,signalAll()}else{// 不是最后一个,进入等待trip.await();// 或 awaitNanos()}关键行为:
- 最后一个到达的线程负责:
- 执行
barrierCommand(如合并结果); - 调用
trip.signalAll()唤醒所有等待者; - 创建新一代(
nextGeneration())。
- 执行
- 其他线程:阻塞在
trip.await(),直到被唤醒。
三、关键特性
1.All-or-Nothing(全有或全无)
- 如果任何一个线程在等待时被中断、超时、或调用
reset(), - 那么所有等待的线程都会抛出
BrokenBarrierException, - 并且 barrier 进入broken 状态。
这保证了“要么全部通过,要么全部失败”,避免部分线程卡住。
2.Barrier Action(屏障动作)
- 可在构造时传入一个
Runnable:newCyclicBarrier(N,()->mergeResults()); - 该动作由最后一个到达的线程执行,在唤醒其他人之前。
- 常用于:汇总本轮结果、更新全局状态。
3.返回到达序号
intarrivalIndex=barrier.await();- 返回值:
parties - 1表示第一个到达,0表示最后一个。 - 可用于:指定某个线程(如
if (await() == 0))执行日志、清理等操作。
4.可重置(reset)
barrier.reset();// 立即打破当前轮次,开启新轮次- 所有当前等待的线程会收到
BrokenBarrierException。 - 之后可以重新开始新一轮同步。
四、与CountDownLatch的本质区别
| 特性 | CyclicBarrier | CountDownLatch |
|---|---|---|
| 用途 | 多线程互相等待(N 对 N) | 一个/多个线程等其他线程完成(1 对 N 或 N 对 1) |
| 可重用 | ✅ 是(cyclic) | ❌ 否(one-shot) |
| 触发者 | 所有参与线程自己调用await() | 其他线程调用countDown() |
| 屏障动作 | ✅ 支持(最后一个线程执行) | ❌ 不支持 |
| 失败模型 | All-or-nothing(全失败) | 单独失败不影响他人 |
| 底层实现 | ReentrantLock + Condition | AQS 共享模式 |
📌简单记:
- 用
CountDownLatch:“我等你们干完”(协调者模式)。- 用
CyclicBarrier:“我们互相等,一起走”(协作模式)。
五、内存可见性(Memory Consistency)
Java 内存模型保证以下happens-before关系:
线程 A 在
await()之前的操作
→happens-before
屏障动作(barrier action)的执行
→happens-before
其他线程从await()返回后的操作
这意味着:
- 所有线程在
await()前写入的共享数据, - 在屏障动作中可见,
- 并且在其他线程继续执行后也可见。
✅ 无需额外同步!
六、使用示例回顾(你的代码)
// N 个 worker 线程并行处理矩阵行barrier=newCyclicBarrier(N,()->mergeRows(...));Worker.run(){while(!done()){processRow(myRow);barrier.await();// 等待所有行处理完}}- 每轮:所有线程处理一行 → 等待 → 合并结果 → 下一轮。
- 如果
mergeRows()发现解已找到,done()返回 true,线程退出。 - 完美体现“循环同步 + 屏障动作”。
七、总结:一句话理解 CyclicBarrier
它是一个可重复使用的“集合点”:N 个线程各自完成任务后在此汇合,全部到齐才一起继续;支持在汇合时执行一个汇总操作,并且任何一人掉队(中断/超时)则全员失败。
它的设计精妙之处在于:
- 用
Generation隔离不同轮次; - 用
ReentrantLock + Condition实现高效等待/唤醒; - 提供强一致性语义和灵活的扩展点(barrier action)。
这也是 Doug Lea 并发设计的经典之作。