Phaser是支持动态增减参与者的多阶段同步工具,区别于CountDownLatch(一次性)和CyclicBarrier(固定参与者);需显式register()注册、arriveAndDeregister()注销,且phase单调递增。

Phaser 是什么,和 CountDownLatch/CyclicBarrier 有什么区别
Phaser 不是“增强版的 CyclicBarrier”,它本质是可动态注册/注销参与者的分阶段同步点。CountDownLatch 一次性、不可重置;CyclicBarrier 固定线程数、阶段间无法增减参与者;而 Phaser 允许在运行时调用 register() 或 arriveAndDeregister() 改变当前阶段的等待人数,且能通过 getPhase() 区分不同阶段——这才是“多阶段”的底层支撑。
常见错误现象:用 Phaser 替代 CyclicBarrier 却没调用 register() 就直接 arriveAndAwaitAdvance(),抛出 IllegalStateException: Not registered。
- 每个线程首次参与前必须显式
register()(构造时传入初始数量也算注册) - 如果某线程只参与前两阶段,第三阶段前应调用
arriveAndDeregister(),否则会卡住后续阶段 -
onAdvance(int phase, int registeredParties)可被重写,用于在阶段切换时做清理或条件判断——比如 phase == 2 时检查某个共享变量是否达标
如何用 Phaser 实现“前两阶段 5 个线程,第三阶段仅 3 个线程”
这是典型的动态参与者场景。关键不是“怎么等”,而是“谁该等、谁不该等”。Phaser 本身不控制线程生命周期,它只响应 arrive* 系列调用;线程是否继续参与下一阶段,完全由你代码决定。
使用场景:数据预处理(5 节点并行读取)→ 校验聚合(3 节点做一致性检查)→ 提交(单节点落库)。
立即学习“Java免费学习笔记(深入)”;
- 初始化时用
new Phaser(5),表示第一阶段期望 5 个参与者 - 每个线程执行完第一阶段后调用
phaser.arriveAndAwaitAdvance() - 进入第二阶段前,2 个线程调用
phaser.arriveAndDeregister(),剩下 3 个继续调用arriveAndAwaitAdvance() - 第三阶段开始前,再让 2 个线程 deregister,只剩 1 个调用
arriveAndAwaitAdvance()即可推进
注意:不能靠“跳过 await”来实现退出——未 arrive 的线程不会被计入当前阶段计数,会导致其他线程永远阻塞。
arriveAndAwaitAdvance() 和 arrive() 的性能与语义差异
arriveAndAwaitAdvance() 是最常用但最重的操作:它既登记到达、又阻塞等待所有同阶段参与者完成;而 arrive() 只登记到达、立即返回,不等待。选错会导致逻辑错乱或死锁。
常见错误现象:在循环中混用二者,比如某线程在阶段 A 用 arrive(),其他线程用 arriveAndAwaitAdvance(),结果前者不等,后者等不到它,整个阶段 hang 住。
- 需要同步推进到下一阶段 → 一律用
arriveAndAwaitAdvance() - 只需要“通知我到了”,后续逻辑不依赖阶段切换(如上报进度、打日志)→ 用
arrive() -
arriveAndDeregister()是arrive()+ 注销,同样不阻塞,适合“做完就走”的线程 - 频繁调用
arriveAndAwaitAdvance()在高并发下可能比CyclicBarrier略慢,因Phaser需维护更多状态(phase、party 数、父 phaser 等)
Phaser 的父子关系和内存泄漏风险
当创建 Phaser 时传入另一个 Phaser 作为 parent,子 phaser 的每次 arriveAndAwaitAdvance() 会向上委托到父级——这能减少顶层竞争,但也引入隐式强引用链。
容易踩的坑:把局部 Phaser 设为某个 long-lived 对象的子 phaser,却忘了在业务结束时调用 phaser.forceTermination() 或确保其被 GC;子 phaser 不终止,父级就一直持有对其的引用,导致内存泄漏。
- 短生命周期任务(如一次 HTTP 请求处理)尽量不用父子结构,避免管理成本
- 必须用父子时,确保子
Phaser在任务结束前调用forceTermination(),尤其在线程池中复用线程的场景 -
isTerminated()返回 true 后,所有 arrive 操作立即返回,不再影响 phase 推进
复杂点在于 phase 值本身不重置,而是单调递增;一旦忘记终止或 deregister,残留的已注销线程仍会参与 phase 计数判断——这种 bug 很难通过日志发现,只能靠 getRegisteredParties() 定期采样验证。


















