Phaser 原理
候选人小卫在面试蚂蚁 P6 时,面试官问道:
"JDK 7 的 Phaser 是什么?它比 CyclicBarrier 强在哪里?"
小卫说:"Phaser 可以动态调整参与方数量..."面试官追问:"arrive 和 arriveAndAwaitAdvance 有什么区别?"
小卫答不上来。面试官继续:"Phaser 怎么实现多阶段同步?"
小卫彻底卡住了...
一、核心问题:Phaser 原理 🟡
1.1 问题拆解
第一层:定位与优势(是什么?)
"Phaser 和 CountDownLatch/CyclicBarrier 有什么区别?"
考察点:动态注册、多阶段、继承
第二层:核心方法(怎么用?)
"arrive、arriveAndAwaitAdvance、register 的区别是什么?"
考察点:到达但不阻塞、到达并阻塞、动态注册
第三层:多阶段同步(怎么做到?)
"Phaser 的 phase 和 generation 是什么?"
考察点:phase 递增、循环重置
第四层:继承注册(高级特性)
"Phaser 怎么实现父子 barrier 的继承?"
考察点:parent 引用、reached 计数
1.2 ❌ 错误示范
候选人原话 A:"Phaser 就是功能更强的 CyclicBarrier,没什么特别的。"
问题诊断:Phaser 比 CyclicBarrier 强大得多——动态注册参与方、多阶段同步(多个同步点)、父子继承、在到达时可选地阻塞或不阻塞。
候选人原话 B:"Phaser 的 arrive() 会阻塞等待其他参与方。"
问题诊断:arrive() 只是到达并递减计数,不阻塞。arriveAndAwaitAdvance() 才阻塞等待所有参与方到达。
1.3 标准回答
P5 级别:Phaser 的定位
Phaser vs CyclicBarrier vs CountDownLatch:
P6 级别:核心方法
参与方注册:
Phaser phaser = new Phaser(3); // 初始 3 个参与方
// 动态注册
phaser.register(); // 增加一个参与方
phaser.bulkRegister(5); // 批量注册 5 个参与方
// 注销(当参与方不再需要同步时)
phaser.arriveAndDeregister(); // 到达并注销
arrive vs arriveAndAwaitAdvance:
// arrive():到达,但不等其他参与方
phaser.arrive(); // 计数 -1,不阻塞
// arriveAndAwaitAdvance():到达并等待其他参与方
phaser.arriveAndAwaitAdvance(); // 阻塞,直到所有参与方到达
// awaitAdvance(phase):如果 phaser 已进入下一阶段,返回
// 否则不阻塞,立即返回
int currentPhase = phaser.awaitAdvance(currentPhase);
多阶段同步示例:
Phaser phaser = new Phaser(3);
// 阶段 0:加载数据
for (int i = 0; i < 3; i++) {
final int id = i;
new Thread(() -> {
load(id); // 阶段 0 任务
phaser.arriveAndAwaitAdvance(); // 等待其他线程完成阶段 0
process(id); // 阶段 1 任务
phaser.arriveAndAwaitAdvance(); // 等待其他线程完成阶段 1
finalize(id); // 阶段 2 任务
phaser.arriveAndDeregister(); // 完成,注销
}).start();
}
P7 级别:phase 和状态管理
phase 的概念:
Phaser 使用 phase(代次)而不是 generation。phase 从 0 开始,每次所有参与方到达后递增,到达 Integer.MAX_VALUE 后溢出归零。
// Phaser 的核心状态
private volatile long state;
// state 编码:
// - 低 16 位:未到达计数(undelivered)
// - 中 16 位:已到达计数(arrived)
// - 高 32 位:phase 代次
// arrive() 核心逻辑
private int doArrive(int adjust) {
int p = (int)(state >>> 32); // 当前 phase
long nc = state + adjust; // adjust = -1 (arrive) 或 -(1+parties) (deregister)
state = nc;
if (nc == 0) { // 所有参与方到达
releaseWaiters(p); // 唤醒等待线程
return nextPhase(); // 进入下一 phase
}
return p;
}
onAdvance 回调:
// 每个阶段完成后调用
protected boolean onAdvance(int phase, int registeredParties) {
// return true: Phaser 终止(类似 CyclicBarrier 被 reset)
// return false: 继续下一阶段
return registeredParties == 0; // 无参与方时终止
}
// 自定义 onAdvance
Phaser phaser = new Phaser(3) {
protected boolean onAdvance(int phase, int registeredParties) {
System.out.println("阶段 " + phase + " 完成");
return registeredParties == 0;
}
};
【面试官心理】
这道题我能问到 P7 级别,是因为 Phaser 涉及了 JDK 7 并发工具的演进、多阶段同步的编程模型。相比 CyclicBarrier 的单阶段限制,Phaser 的多阶段特性是高级并发场景的利器。
1.4 追问升级
追问 1:Phaser 的树形结构(父子 Phaser)是什么?
JDK 7 引入的树形 Phaser 用于减少单一 Phaser 的竞争:
// 将大量参与方分布在多个子 Phaser 上
Phaser parent = new Phaser(1);
Phaser child1 = new Phaser(parent, N); // 子 barrier
Phaser child2 = new Phaser(parent, M);
子 Phaser 同步时,只影响其 parent,不需要所有参与方在单一 barrier 上等待。
追问 2:Phaser 和 ForkJoinPool 的区别?
Phaser 是同步原语,ForkJoinPool 是任务调度框架。两者可以结合使用——ForkJoinPool 中的任务在到达同步点时使用 Phaser 等待。
二、生产避坑 🟢
2.1 忘记注销导致永久等待
Phaser phaser = new Phaser(3);
new Thread(() -> {
if (someCondition()) {
> // ❌ 条件分支导致部分线程未到达
> phaser.arrive(); // 但未 deregister
> return;
> }
> phaser.arriveAndAwaitAdvance();
}).start();
// 如果部分线程在条件分支中离开,没有 deregister,
// 剩余线程会永远等待
解决:使用 arriveAndDeregister() 或确保所有分支都到达。
2.2 阶段数过多导致的溢出
phase 是 32 位整数,溢出后归零。在极端长时间运行的服务中(运行超过 68 年),phase 可能溢出归零。
三、Phaser 的高级用法 🟢
3.1 动态调整并发度
Phaser phaser = new Phaser(1); // 初始 1 个参与方
for (int i = 0; i < 10; i++) {
final int id = i;
phaser.register(); // 动态注册
new Thread(() -> {
process(id);
phaser.arriveAndAwaitAdvance();
phaser.arriveAndDeregister(); // 完成,注销
}).start();
}
phaser.arriveAndAwaitAdvance(); // 等待所有动态注册的任务完成
3.2 替代方案对比
在 JDK 7+ 环境中,Phaser 是最灵活的屏障。如果需要兼容 JDK 6,只能用 CyclicBarrier 或 CountDownLatch。
💡
面试加分点:能说出"Phaser 的 state 使用 long 类型编码多个字段(phase、arrived、parties),通过位运算高效地读写",说明他对 JDK 的位编码优化有了解。
⚠️
面试陷阱:被问到"Phaser 的 arrive() 返回值是什么",很多人会说"当前 phase"。准确答案是:arrive() 返回下一 phase 号(如果当前是 phase P,返回 P+1),而不是当前 phase。这是因为 arrive() 调用时 phase 可能已经变化。