从 ForkJoinPool 的 Compensate 看并发框架的线程补偿思想

大家好,我是桦说编程。

本文从 ForkJoinPool 的 tryCompensate 出发,结合 CompletableFuture 中的类似机制,帮你理解并发框架中「线程阻塞时如何保证系统持续推进」这一核心设计思想。

问题背景

线程池中,当一个工作线程因为等待某个结果而阻塞时,它就「消失」了------不再能执行任何任务。如果多个线程同时阻塞,线程池可能陷入「所有线程都在等、没人干活」的死局。

核心矛盾:线程要等结果,但等的过程中不能让整个池子停摆。

ForkJoinPool 给出的答案是 Compensate(补偿):你要阻塞?可以,但系统会补偿一个线程来顶替你。

这个思想并非 ForkJoinPool 独有,CompletableFuture 中也有类似的补偿机制。

一、ForkJoinPool:直接的线程补偿

触发时机

当 worker 线程调用 ForkJoinTask.join() 等待子任务结果时,经过 help-stealing 尝试仍未完成,就会走到补偿逻辑:

java 复制代码
// 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:补偿决策树

java 复制代码
// 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:身兼二职

java 复制代码
// 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:先自旋,再补偿阻塞

java 复制代码
// 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:补偿的桥梁

java 复制代码
// 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 补偿的完整链路

scss 复制代码
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 的补偿能力

四、统一的设计哲学

两个框架的补偿机制,本质上都在回答同一个问题:

一个线程即将不可用时,如何确保系统整体仍能推进?

答案的核心模式是一致的:

  1. 尽量不阻塞------先自旋、先帮忙(help-steal)
  2. 阻塞前先安排后事------补偿线程 / 注册 ManagedBlocker
  3. 醒来后恢复状态------恢复活跃计数

五、完整示例

java 复制代码
/**
 * 演示两种并发框架中的补偿思想
 *
 * <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 补偿");
    }
}

总结

  • ForkJoinPooltryCompensate 是最直接的补偿------你要阻塞,池子就补一个线程,确保并行度不降
  • CompletableFuture 通过 ManagedBlocker 接口桥接 ForkJoinPool 的补偿能力,在 get() 阻塞时不会拖垮线程池
  • 两者共享同一设计哲学:线程阻塞不可怕,可怕的是阻塞时没人顶上

如果这篇文章对你有帮助,欢迎关注我,持续分享高质量技术干货,助你更快提升编程能力。

相关推荐
格砸2 小时前
从入门到辞职|从ChatGPT到OpenClaw,跟上智能时代的进化
前端·人工智能·后端
蝎子莱莱爱打怪3 小时前
GitLab CI/CD + Docker Registry + K8s 部署完整实战指南
后端·docker·kubernetes
躺平大鹅3 小时前
Java面向对象入门(类与对象,新手秒懂)
java
哈密瓜的眉毛美3 小时前
零基础学Java|第三篇:DOS 命令、转义字符、注释与代码规范
后端
用户60572374873083 小时前
AI 编码助手的规范驱动开发 - OpenSpec 初探
前端·后端·程序员
哈密瓜的眉毛美4 小时前
零基础学Java|第二篇:Java 核心机制与第一个程序:从 JVM 到 Hello World
后端
用户8307196840824 小时前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者4 小时前
RocketMQ 集群介绍
后端·消息队列·rocketmq
初次攀爬者4 小时前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq