大家好,我是桦说编程。
本文从 ForkJoinPool 的
tryCompensate出发,结合 CompletableFuture 中的类似机制,帮你理解并发框架中「线程阻塞时如何保证系统持续推进」这一核心设计思想。
问题背景
线程池中,当一个工作线程因为等待某个结果而阻塞时,它就「消失」了——不再能执行任何任务。如果多个线程同时阻塞,线程池可能陷入「所有线程都在等、没人干活」的死局。
核心矛盾:线程要等结果,但等的过程中不能让整个池子停摆。
ForkJoinPool 给出的答案是 Compensate(补偿):你要阻塞?可以,但系统会补偿一个线程来顶替你。
这个思想并非 ForkJoinPool 独有,CompletableFuture 中也有类似的补偿机制。
一、ForkJoinPool:直接的线程补偿
触发时机
当 worker 线程调用 ForkJoinTask.join() 等待子任务结果时,经过 help-stealing 尝试仍未完成,就会走到补偿逻辑:
// ForkJoinPool.awaitJoin(核心流程,略去无关代码)
final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
int s = 0;
if (task != null && w != null) {
for (;;) {
if ((s = task.status) < 0) // 任务已完成,直接返回
break;
// 先尝试帮忙:窃取任务执行,尝试推进进度
if (cc != null)
helpComplete(w, cc, 0);
else if (w.base == w.top || w.tryRemoveAndExec(task))
helpStealer(w, task);
if ((s = task.status) < 0) // 帮完再看一眼
break;
// 帮不了了,准备阻塞——但阻塞前必须补偿
if (tryCompensate(w)) {
task.internalWait(ms); // 阻塞等待
U.getAndAddLong(this, CTL, AC_UNIT); // 醒来后恢复活跃计数
}
}
}
return s;
}
关键节奏:先帮忙 → 帮不了再补偿 → 补偿成功才允许阻塞 → 醒来后恢复。
tryCompensate:补偿决策树
// ForkJoinPool.tryCompensate(核心逻辑)
private boolean tryCompensate(WorkQueue w) {
boolean canBlock;
WorkQueue[] ws; long c; int m, pc, sp;
if (w == null || w.qlock < 0 ||
(ws = workQueues) == null || (m = ws.length - 1) <= 0 ||
(pc = config & SMASK) == 0)
canBlock = false; // ① 基础校验不过,不补偿
else if ((sp = (int)(c = ctl)) != 0)
canBlock = tryRelease(c, ws[sp & m], 0L); // ② 有空闲线程?唤醒它来顶替
else {
int ac = (int)(c >> AC_SHIFT) + pc; // 活跃线程数
int tc = (short)(c >> TC_SHIFT) + pc; // 总线程数
// ... 校验是否所有线程确实都在忙 ...
if (tc >= pc && ac > 1 && w.isEmpty()) {
// ③ 线程够用且活跃数>1,直接减少活跃计数(不补偿也行)
long nc = ((AC_MASK & (c - AC_UNIT)) | (~AC_MASK & c));
canBlock = U.compareAndSwapLong(this, CTL, c, nc);
} else if (tc >= MAX_CAP || ...) {
throw new RejectedExecutionException( // ④ 到上限了,拒绝
"Thread limit exceeded replacing blocked worker");
} else {
// ⑤ 核心补偿:创建一个新的 worker 线程
boolean add = false; int rs;
long nc = ((AC_MASK & c) |
(TC_MASK & (c + TC_UNIT))); // 增加总线程数,但不增加活跃数
if (((rs = lockRunState()) & STOP) == 0)
add = U.compareAndSwapLong(this, CTL, c, nc);
unlockRunState(rs, rs & ~RSLOCK);
canBlock = add && createWorker();
}
}
return canBlock;
}
补偿策略的优先级:
| 优先级 | 策略 | 说明 |
|---|---|---|
| 1 | 唤醒空闲线程 | 成本最低,复用已有线程 |
| 2 | 减少活跃计数 | 活跃线程够用,标记自己「不活跃」即可 |
| 3 | 创建新线程 | 最后手段,真正新增一个 worker |
精妙之处:新建的补偿线程只增加 TC(总线程数)不增加 AC(活跃计数),因为当前线程马上要阻塞(AC 会减 1),新线程启动后 AC 会加 1,一减一加刚好平衡。
二、CompletableFuture:借助 ManagedBlocker 触发补偿
CompletableFuture 的 get() / join() 可能在 ForkJoinPool 的 worker 线程中被调用。如果直接阻塞,就会吃掉池子的并行度。它的解决方案是实现 ManagedBlocker 接口,借助 ForkJoinPool 的补偿机制。
Signaller:身兼二职
// CompletableFuture.Signaller(核心字段和方法)
static final class Signaller extends Completion
implements ForkJoinPool.ManagedBlocker {
long nanos;
final long deadline;
volatile int interruptControl;
volatile Thread thread;
// ManagedBlocker 接口:是否可以不阻塞?
public boolean isReleasable() {
if (thread == null) // 已被唤醒
return true;
if (Thread.interrupted()) { // 被中断
interruptControl = -1;
return true;
}
if (deadline != 0L && ...) { // 超时
thread = null;
return true;
}
return false;
}
// ManagedBlocker 接口:执行阻塞
public boolean block() {
if (isReleasable())
return true;
else if (deadline == 0L)
LockSupport.park(this);
else if (nanos > 0L)
LockSupport.parkNanos(this, nanos);
return isReleasable();
}
// Future 完成时唤醒等待线程
final CompletableFuture<?> tryFire(int ignore) {
Thread w;
if ((w = thread) != null) {
thread = null;
LockSupport.unpark(w);
}
return null;
}
}
waitingGet:先自旋,再补偿阻塞
// CompletableFuture.waitingGet(核心流程)
private Object waitingGet(boolean interruptible) {
Signaller q = null;
boolean queued = false;
int spins = -1;
Object r;
while ((r = result) == null) {
if (spins < 0)
spins = SPINS; // 多核 256,单核 0
else if (spins > 0) {
if (ThreadLocalRandom.nextSecondarySeed() >= 0)
--spins; // ① 自旋等待
}
else if (q == null)
q = new Signaller(interruptible, 0L, 0L); // ② 创建信号器
else if (!queued)
queued = tryPushStack(q); // ③ 注册到完成通知链
else if (interruptible && q.interruptControl < 0) {
q.thread = null;
cleanStack();
return null;
}
else if (q.thread != null && result == null) {
// ④ 关键:通过 managedBlock 触发 ForkJoinPool 的补偿
ForkJoinPool.managedBlock(q);
}
}
// ...
return r;
}
managedBlock:补偿的桥梁
// ForkJoinPool.managedBlock
public static void managedBlock(ManagedBlocker blocker)
throws InterruptedException {
Thread t = Thread.currentThread();
if (t instanceof ForkJoinWorkerThread) {
// 在 ForkJoinPool 内:触发补偿!
ForkJoinPool p = ((ForkJoinWorkerThread) t).pool;
WorkQueue w = ((ForkJoinWorkerThread) t).workQueue;
while (!blocker.isReleasable()) {
if (p.tryCompensate(w)) { // 补偿一个线程
try {
do {} while (!blocker.isReleasable() &&
!blocker.block());
} finally {
U.getAndAddLong(p, CTL, AC_UNIT); // 醒来恢复活跃计数
}
break;
}
}
} else {
// 不在 ForkJoinPool 内:普通阻塞,无需补偿
do {} while (!blocker.isReleasable() && !blocker.block());
}
}
CompletableFuture 补偿的完整链路:
CompletableFuture.get()
→ waitingGet()
→ 自旋 256 次
→ 创建 Signaller(实现 ManagedBlocker)
→ ForkJoinPool.managedBlock(signaller)
→ tryCompensate() // 补偿一个线程
→ signaller.block() // 安心阻塞
→ 被唤醒后 AC_UNIT++ // 恢复活跃计数
三、两者对比
| 维度 | ForkJoinPool | CompletableFuture |
|---|---|---|
| 补偿触发 | worker 线程要阻塞时 | get() 在 FJP worker 中调用时 |
| 补偿方式 | 唤醒空闲线程 / 创建新线程 | 委托 FJP 的 tryCompensate |
| 补偿目的 | 维持并行度 | 避免 FJP 并行度下降 |
| 阻塞前动作 | 先 help-steal,再补偿 | 先自旋,再 managedBlock |
| 核心思想 | 你要睡,我补人 | 借用 FJP 的补偿能力 |
四、统一的设计哲学
两个框架的补偿机制,本质上都在回答同一个问题:
一个线程即将不可用时,如何确保系统整体仍能推进?
答案的核心模式是一致的:
- 尽量不阻塞——先自旋、先帮忙(help-steal)
- 阻塞前先安排后事——补偿线程 / 注册 ManagedBlocker
- 醒来后恢复状态——恢复活跃计数
五、完整示例
/**
* 演示两种并发框架中的补偿思想
*
* <p>1. ForkJoinPool tryCompensate:worker 阻塞时补偿新线程维持并行度
* <p>2. CompletableFuture ManagedBlocker:在 FJP 中 get() 时触发补偿
*
* @author linzi
* @date 2026/03/02
*/
public class CompensateDemo {
/**
* 演示1:ForkJoinPool 的补偿机制
* 当 worker 线程 join 子任务时,tryCompensate 会补偿线程维持并行度
*/
static void demoForkJoinCompensate() {
System.out.println("=== ForkJoinPool Compensate Demo ===");
// 并行度设为2,方便观察补偿行为
ForkJoinPool pool = new ForkJoinPool(2);
ForkJoinTask<Integer> result = pool.submit(new RecursiveTask<Integer>() {
@Override
protected Integer compute() {
RecursiveTask<Integer> left = new RecursiveTask<Integer>() {
@Override
protected Integer compute() {
System.out.println(" left task on: " + Thread.currentThread().getName());
sleep(100);
return 1;
}
};
RecursiveTask<Integer> right = new RecursiveTask<Integer>() {
@Override
protected Integer compute() {
System.out.println(" right task on: " + Thread.currentThread().getName());
sleep(100);
return 2;
}
};
left.fork();
right.fork();
// join 时当前 worker 会阻塞
// 内部流程:awaitJoin → helpStealer → tryCompensate → internalWait
int l = left.join();
int r = right.join();
System.out.println(" joined on: " + Thread.currentThread().getName());
return l + r;
}
});
System.out.println(" result = " + result.join());
pool.shutdown();
System.out.println();
}
/**
* 演示2:CompletableFuture 通过 ManagedBlocker 触发 FJP 补偿
* 在 ForkJoinPool 中 get() 时,Signaller 作为 ManagedBlocker 触发 tryCompensate
*/
static void demoCompletableFutureCompensate() {
System.out.println("=== CompletableFuture ManagedBlocker Demo ===");
ForkJoinPool pool = new ForkJoinPool(2);
pool.submit(() -> {
System.out.println(" outer task on: " + Thread.currentThread().getName());
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println(" async task on: " + Thread.currentThread().getName());
sleep(200);
return "done";
}, pool);
// 在 FJP worker 线程中调用 join()
// 内部流程:waitingGet → 自旋 → Signaller → managedBlock → tryCompensate
String result = future.join();
System.out.println(" got result: " + result + " on " + Thread.currentThread().getName());
}).join();
pool.shutdown();
System.out.println();
}
private static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
demoForkJoinCompensate();
demoCompletableFutureCompensate();
System.out.println("=== Summary ===");
System.out.println("ForkJoinPool : 线程阻塞 → tryCompensate → 唤醒/创建线程维持并行度");
System.out.println("CompletableFuture : get()/join() → ManagedBlocker → 委托 FJP 补偿");
}
}
总结
- ForkJoinPool 的
tryCompensate是最直接的补偿——你要阻塞,池子就补一个线程,确保并行度不降 - CompletableFuture 通过
ManagedBlocker接口桥接 ForkJoinPool 的补偿能力,在get()阻塞时不会拖垮线程池 - 两者共享同一设计哲学:线程阻塞不可怕,可怕的是阻塞时没人顶上
如果这篇文章对你有帮助,欢迎关注我,持续分享高质量技术干货,助你更快提升编程能力。