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

维度PhaserCyclicBarrierCountDownLatch
参与方数量动态(可随时注册/注销)固定(构造时指定)固定(构造时指定)
同步点数量多阶段(多个同步点)单阶段(需要重建)单阶段(不可复用)
阻塞方式arrive/await 可选统一 await统一 await
继承支持父子 barrier不支持不支持
用途多阶段多参与方多线程单阶段主线程等待子任务

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 可能已经变化。