Phaser 是 Java 并发包中支持动态注册/注销线程的分阶段同步工具,区别于 CountDownLatch(不可变计数)和 CyclicBarrier(固定线程数);需显式 register() 后才能 arriveAndAwaitAdvance(),否则抛 IllegalStateException。

Phaser 是什么,和 CountDownLatch、CyclicBarrier 有什么区别
Phaser 是 Java 并发包中一个灵活的同步屏障,专为「动态参与线程数」设计。它不像 CountDownLatch 那样初始化后不可变,也不像 CyclicBarrier 要求每次等待的线程数固定——Phaser 允许线程在任意阶段注册(register())、到达(arriveAndAwaitAdvance())、注销(arriveAndDeregister()),还能支持分阶段推进(phase 编号递增)。
常见误用是把它当 CyclicBarrier 的替代品却忽略注册时机:没调 register() 就直接 arriveAndAwaitAdvance(),会抛 IllegalStateException: Not registered。
使用场景包括: - 分批处理任务,每批线程数不固定(如工作线程按需拉起) - 树形/图状并行结构中子节点动态汇合 - 多轮迭代计算,每轮参与线程可能新增或退出
如何正确注册和注销线程
Phaser 不自动注册线程,必须显式调用 register()(返回当前 phase)或构造时指定初始参与者数。最安全的做法是:每个线程在执行逻辑前先注册。
Phaser phaser = new Phaser(1); // 主线程作为初始参与者
// 工作线程启动前:
phaser.register(); // 显式注册自己
try {
// 执行阶段任务
doWork();
phaser.arriveAndAwaitAdvance(); // 等待本阶段所有已注册线程到达
} finally {
phaser.arriveAndDeregister(); // 明确退出,避免泄漏
}注意点:
-
register()和deregister()必须成对出现,否则getRegisteredParties()会持续增长,导致后续arriveAndAwaitAdvance()永远阻塞 - 不要在
finally块里无条件deregister():如果线程注册失败(比如Phaser已终止),deregister()会抛IllegalStateException - 注册后立即 deregister 是无效操作,会导致计数异常;应确保至少有一次
arrive*调用
怎么用 arriveAndAwaitAdvance() 控制多阶段同步
arriveAndAwaitAdvance() 是核心协调点:它让当前线程抵达当前 phase,并阻塞直到所有已注册但未注销的线程都抵达同一 phase,然后所有线程一起进入下一 phase(phase 自动 +1)。
关键行为:
- 返回值是「进入的 phase 编号」,可用于判断是否是某一轮的首/末次同步
- 如果某线程在其他线程还没到达时就提前 deregister,剩余线程仍会正常 await,只是该 phase 的参与者数减少
- 若所有已注册线程都 deregister,
isTerminated()变为 true,后续arriveAndAwaitAdvance()会立即返回(不再阻塞)
示例:两轮处理,第二轮只有部分线程继续参与
// 第一轮:5 个线程全部注册并同步
phaser.register();
doPhaseOne();
phaser.arriveAndAwaitAdvance(); // phase 0 → 1
<p>// 第二轮:仅其中 3 个线程继续,2 个调用 deregister 后退出
if (shouldContinue) {
doPhaseTwo();
phaser.arriveAndAwaitAdvance(); // phase 1 → 2
} else {
phaser.arriveAndDeregister(); // 主动退出,不影响其他线程推进
}容易被忽略的终止与异常处理细节
Phaser 的终止状态(isTerminated())一旦进入就不可逆,且会影响所有后续操作。常见疏漏:
- 主动调用
forceTermination()后,所有未完成的arriveAndAwaitAdvance()会立即返回,但不会抛异常——这可能导致逻辑跳过关键同步点而难以排查 -
onAdvance(int phase, int registeredParties)回调中若抛出未捕获异常,整个Phaser会强制终止,且异常不会向调用线程传播 - 在回调中不应再调用
register()或deregister(),否则可能引发ConcurrentModificationException
建议做法:
- 把
Phaser生命周期绑定到明确作用域(如单次任务对象),避免跨多个不相关任务复用 - 监控
getUnarrivedParties()和getRegisteredParties(),尤其在调试阶段打印日志 - 如果某线程因异常退出,应在 catch 块中补调
arriveAndDeregister(),防止“幽灵参与者”卡住 phase 推进
动态数量的本质不是“数量可变”,而是“注册与注销时机由业务逻辑决定”——这点比 phase 编号更关键。


















