phaser 是支持动态注册/注销参与者的分阶段同步工具,与 countdownlatch(一次性)和 cyclicbarrier(固定参与者循环等待)有本质区别:它允许每阶段参与者数量不同、线程可中途加入或退出,且无 reset 但可 forcetermination。

Phaser 是什么,和 CountDownLatch、CyclicBarrier 有什么本质区别
Phaser 不是“增强版的 CyclicBarrier”,它压根就不是为固定阶段循环等待设计的。它的核心价值在于:允许线程在任意阶段动态注册/注销,且每个阶段可有不同数量的参与者。如果你的需求是“前两轮要 A/B/C 一起等,第三轮只让 A 和 B 等,第四轮再加个 D”,CountDownLatch 和 CyclicBarrier 都得绕弯子甚至改逻辑;而 Phaser 原生支持——靠的是 register() 和 arriveAndDeregister()。
常见错误现象:Phaser 初始化时传了初始参与者数,但后续没调用 register() 就直接 arriveAndAwaitAdvance(),结果卡死或抛 IllegalStateException(因为当前未到达的参与者数为负)。
- 初始化时传
0表示“不预设任何参与者”,所有线程必须显式register() - 一个线程调用
arriveAndDeregister()后,它对后续阶段就彻底“隐身”了,不会被计入getRegisteredParties() -
CyclicBarrier的reset()会中断所有等待线程;Phaser没有 reset,但可通过forceTermination()快速退出,之后所有操作都立即返回负值
怎么安全地让线程中途加入或退出某个阶段
动态调整的关键不在“怎么加”,而在“加完立刻参与当前阶段”还是“从下一阶段开始算”。默认行为是后者——调用 register() 后,该线程不会自动等待当前阶段结束,而是从下一轮才纳入同步点。这是最容易踩坑的地方。
使用场景:比如数据加载阶段已进行到一半,新来的 worker 要立刻跟上进度,不能等下一阶段。
立即学习“Java免费学习笔记(深入)”;
- 若需“加入即同步当前阶段”,必须配合
arriveAndAwaitAdvance()手动对齐:phaser.register(); phaser.arriveAndAwaitAdvance(); - 若只是临时参与某一轮,用完就走,优先选
arriveAndDeregister(),别用arrive()+ 单独 deregister,容易漏掉 - 多个线程并发调用
register()是线程安全的,但getPhase()返回值可能在你读取后立刻变化,不要拿它做条件判断依据
Phaser 的 phase 值溢出和终止状态怎么处理
Phaser 的 phase 是 int 类型,理论上最多执行约 21 亿轮就会溢出回 0。实际中几乎不会撞上,但一旦发生,getPhase() 突然变小可能被误判为“阶段倒退”,导致逻辑错乱。
更现实的问题是:没人主动调 forceTermination(),但某个线程因异常提前退出,又没 deregister,结果整个 Phaser 卡在那一阶段再也 advance 不了。
- 检查是否终止:用
isTerminated(),而不是靠getPhase() == -1(终止后 phase 返回负值,但具体值不保证是 -1) - 避免依赖 phase 数值做业务判断,比如“phase == 3 就发邮件”,应改用外部状态标记
- 在 finally 块里确保 deregister 或 arrive,尤其在 try-catch 包裹的阶段逻辑中
性能和内存开销比 CyclicBarrier 高吗
单次 await 开销略高,但差别微乎其微(纳秒级),真正影响性能的是你用法是否合理。Phaser 内部用了分段锁 + CAS,比 CyclicBarrier 的单一内部锁更适合高并发动态注册场景。
不过,如果全程参与者数量固定、阶段数明确、无需中途进出,硬上 Phaser 反而多了一层抽象,还多了 register() 调用开销。
- 固定 5 个线程跑 10 轮:用
CyclicBarrier更轻量,代码也更直白 - Worker 池动态伸缩,每轮参与数波动大:
Phaser的线性扩展性优势才体现出来 - 注意
Phaser默认构造不带父节点,但如果嵌套使用(如父子任务),父Phaser的 terminate 会级联终止子节点,这个传播行为容易被忽略
phase 溢出概率低,但 deregister 遗漏会导致永久阻塞——这比性能问题更值得花时间盯住。










