ForkJoinPool 源码深度解析:从分治思想到工作窃取的完整实现

🤔 一、道格·李为什么需要一个"能偷工作"的线程池

Java 5 的 ThreadPoolExecutor 解决了线程复用的问题,但它有一个结构性的局限:所有线程共享一个任务队列。当一个线程提交了子任务后阻塞等待子任务结果,而子任务又在同一个队列里等待被执行时,就会发生线程饥饿——等待的线程占着一个槽位但不干活,队列里的子任务没人执行,形成死锁。

这个问题在递归分治算法(把大问题拆成小问题递归求解)中尤为致命。分治算法天然适合并行——子问题之间互不依赖,可以同时计算。但如果每个线程都把子任务扔到共享队列然后等结果,队列很快就会堆满等待被执行的任务而所有线程都在等。

道格·李在 Java 7 中引入 ForkJoinPool 时,核心创新是工作窃取(Work-Stealing):

  • 每个工作线程有自己的双端队列(Deque),线程从自己的队列头部取任务
  • 当一个线程 fork 子任务时,子任务被 push 到该线程自己的队列
  • 当线程自己的队列空了,它会从其他线程的队列尾部窃取任务来执行

这个设计解决了两个问题:① 递归 fork 的子任务不会堵塞共享队列;② 快线程不会空等——它会偷慢线程的活来干。ForkJoinPool 也是 Java 8 并行流(parallelStream())的底层引擎。

二、设计思想:分治算法 + 工作窃取

📌 2.1 分治算法(Divide-and-Conquer)

ForkJoinPool 的设计基础是分治算法(Divide-and-Conquer),其核心过程为三个方面:

flowchart TD
%% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %%
classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb;
classDef root fill:#0f172a,stroke:#3b82f6,stroke-width:2.5px,color:#bfdbfe,font-weight:bold;
    ROOT[根任务\n问题规模N]
    ROOT -->|拆分| L1[子任务\n规模N/2]
    ROOT -->|拆分| R1[子任务\n规模N/2]
    L1 -->|继续拆分| L2[子任务\n规模N/4]
    L1 -->|继续拆分| R2[子任务\n规模N/4]
    R1 -->|继续拆分| L3[子任务\n规模N/4]
    R1 -->|继续拆分| R3[子任务\n规模N/4]
    L2 -->|达到阈值\n直接计算| SOLVE1[原子任务]
    R2 -->|达到阈值\n直接计算| SOLVE2[原子任务]
    L3 -->|达到阈值\n直接计算| SOLVE3[原子任务]
    R3 -->|达到阈值\n直接计算| SOLVE4[原子任务]
    SOLVE1 -->|合并| MERGE1[汇总结果]
    SOLVE2 -->|合并| MERGE1
    SOLVE3 -->|合并| MERGE2[汇总结果]
    SOLVE4 -->|合并| MERGE2
    MERGE1 -->|最终合并| FINAL[最终结果]
    MERGE2 -->|最终合并| FINAL

class FINAL,L1,L2,L3,MERGE1,MERGE2,R1,R2,R3,SOLVE1,SOLVE2,SOLVE3,SOLVE4 process;
class ROOT root;
阶段操作说明
Divide(拆分)fork()将大任务递归拆分为小任务,直到达到阈值
Conquer(求解)compute()对原子任务执行实际计算
Combine(合并)join()递归汇总所有子任务的结果

📌 2.2 工作窃取算法(Work-Stealing)

普通的 ThreadPoolExecutor 使用单一共享阻塞队列(BlockingQueue),所有线程竞争同一个队列的头元素,存在单点竞争瓶颈。ForkJoinPool 则采用完全不同的设计:每个工作线程维护自己的双端队列(WorkQueue)

flowchart TD
%% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %%
classDef data fill:#052e16,stroke:#16a34a,stroke-width:2px,color:#bbf7d0,font-weight:bold;
classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb;
    subgraph STA[工作窃取线程池]
        T1[线程1\nWorkQueue1]
        T2[线程2\nWorkQueue2]
        T3[线程3\nWorkQueue3]
        T4[线程4\nWorkQueue4]
    end

    T1 -->|push/pop\nLIFO 本地| T1Q[Task1 Task2 Task3]
    T2 -->|空闲 steal\nFIFO 窃取| T2Q[Task4 Task5]

    T3Q[Task6 Task7 Task8 Task9] -->|被窃取| T1
    T4Q[Task10 Task11] -->|被窃取| T2

    T1Q -.->|base端FIFO| T1Q
    T2Q -.->|base端FIFO| T2Q

class STA,T1,T2,T3,T4 data;
class T1Q,T2Q,T3Q,T4Q process;

工作窃取的关键规则:

操作执行者队列端顺序说明
push队列所属线程top 端LIFO(后进先出)子任务 fork() 时推入自身队列顶部
pop队列所属线程top 端LIFO本地取任务,优先处理最新产生的子任务(数据局部性)
poll其他空闲线程base 端FIFO(先进先出)窃取最早入队的任务,任务粒度通常较大

LIFO 入队 + LIFO 出队(本地)保证热点数据局部性;FIFO 窃取(远程)保证窃取到的任务粒度大,减少窃取频次。这是 ForkJoinPool 高性能的核心原因之一。

🏗️ 三、类继承体系与核心数据结构

📌 3.1 三大模块

flowchart LR
%% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %%
classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2.5px,color:#fce7f3,font-weight:bold;
classDef data fill:#052e16,stroke:#16a34a,stroke-width:2px,color:#bbf7d0,font-weight:bold;
classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb;
    POOL[ForkJoinPool\n线程池-全局调度]
    WORKER[ForkJoinWorkerThread\n工作线程-持有WorkQueue]
    TASK[ForkJoinTask\n任务抽象-轻量Future]
    RT[RecursiveTask\n有返回值]
    RA[RecursiveAction\n无返回值]
    CC[CountedCompleter\n计数完成]

    POOL -->|管理| WORKER
    WORKER -->|持有| WQ[WorkQueue\n双端任务队列]
    WORKER -->|执行| TASK
    TASK --> RT
    TASK --> RA
    TASK --> CC

class POOL,WORKER,WQ data;
class TASK process;
class CC,RA,RT startEnd;

📌 3.2 ForkJoinTask 的 status 字段

ForkJoinTask 最核心的设计是 volatile int status,它用一个 int 承载了任务状态和等待线程信号两种信息:

volatile int status;

// 完成状态(高 4 位,均为负值)
static final int NORMAL      = 0xf0000000;  // 正常完成
static final int CANCELLED   = 0xc0000000;  // 取消
static final int EXCEPTIONAL = 0x80000000;  // 异常

// 信号标记(低位,正值)
static final int SIGNAL      = 0x00010000;  // 有线程在等待
static final int SMASK       = 0x0000ffff;  // 低位掩码(取标记用)

逐字段解读:

  • 高 4 位为完成状态status < 0 表示任务已完成,三种完成状态都是负值(NORMAL=0xf…, CANCELLED=0xc…, EXCEPTIONAL=0x8…)。判断任务是否完成的逻辑非常简洁:status < 0
  • SIGNAL(0x00010000):等待标记,表示有线程正在 join() 该任务。被等待线程在完成时会通过此标记唤醒等待者。
  • SMASK(0x0000ffff):低 16 位掩码,用于提取栈顶线程的索引信息。

⚙️ 3.3 ctl——ForkJoinPool 的核心控制字

ctl 是 ForkJoinPool 中最核心的一个字段,一个 64 位的 long 被切分为四个 16 位子域,用来无锁地管理整个线程池的全局状态:

volatile long ctl;

// 子域位偏移
// |----------|----------|----------|----------|
//  63      48 47      32 31      16 15       0
//      AC         TC         SS        ID

private static final long SP_MASK    = 0xffffffffL;      // 低 32 位掩码
private static final long UC_MASK    = ~SP_MASK;          // 高 32 位掩码

private static final long AC_UNIT    = 0x0001L << 48;     // AC 增量
private static final long TC_UNIT    = 0x0001L << 32;     // TC 增量
private static final long ADD_WORKER = 0x0001L << (32+15); // 需添加工作线标志
flowchart TD
%% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %%
classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb;
classDef data fill:#052e16,stroke:#16a34a,stroke-width:2px,color:#bbf7d0,font-weight:bold;
    subgraph CTL[ctl 64位布局]
        A["AC 高16位\nbits 63-48"]
        B["TC 中高16位\nbits 47-32"]
        C["SS 中低16位\nbits 31-16"]
        D["ID 低16位\nbits 15-0"]
    end
    A --> A1["活跃线程数 - 并行度\nAC = (activeCount - parallelism)"]
    B --> B1["总线程数 - 并行度\nTC = (totalCount - parallelism)"]
    C --> C1["栈顶版本+状态\nstackPred的低16位"]
    D --> D1["栈顶WorkQueue索引\npoolIndex"]

class D1 data;
class A,A1,B,B1,C,C1,CTL,D process;
子域位范围含义符号
AC63 ~ 48活跃工作线程数 - 目标并行度。AC < 0 表示活跃线程不足ctl < 0L
TC47 ~ 32总工作线程数 - 目标并行度。决定是否可以创建新线程
SS31 ~ 16栈顶线程的版本计数和状态(INACTIVE 标记等)
ID15 ~ 0栈顶 WorkQueueworkQueues 数组中的索引

ctl < 0L 即可判断是否需要激活新线程。因为 AC 在高位,当活跃线程数小于并行度时 AC 为负数,整个 64 位值也就为负。这个设计把复杂的比较判断精简为一条 CPU 指令。

📌 3.4 WorkQueue——双端任务队列

WorkQueue 是 ForkJoinPool 最核心的数据结构,每个工作线程持有自己的 WorkQueue 实例:

static final class WorkQueue {
    volatile int scanState;     // 扫描状态:<0 失活(INACTIVE),奇数=SCANNING
    int stackPred;              // 前一个栈顶的 ctl 低 32 位值
    int nsteals;                // 窃取任务计数
    int hint;                   // 窃取者随机索引提示
    int config;                 // 池索引(低16位) + 模式(高16位)
    volatile int qlock;         // 1:锁定中,<0:终止,0:正常
    volatile int base;          // poll端索引(队列头,FIFO出队)
    int top;                    // push端索引(栈顶,LIFO入队)
    ForkJoinTask<?>[] array;    // 任务数组,容量为 2 的幂
    final ForkJoinWorkerThread owner; // 所属线程,SHARED_QUEUE 模式下为 null
    volatile Thread parker;     // 阻塞期间设为 owner
    volatile ForkJoinTask<?> currentJoin;   // 正在被 join 的任务
    volatile ForkJoinTask<?> currentSteal;  // 当前正在执行的窃取任务
}
flowchart TD
%% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %%
classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:2px,color:#ede9fe,font-weight:bold;
classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb;
classDef data fill:#052e16,stroke:#16a34a,stroke-width:2px,color:#bbf7d0,font-weight:bold;
    subgraph WQ[WorkQueue 双端队列]
        BASE[base 端\nFIFO 被窃取端]
        TOP[top 端\nLIFO 本地端]
        direction LR
        BASE ---|base 递增→| E0[Task0]
        E0 --- E1[Task1]
        E1 --- E2[Task2]
        E2 ---|←top 递增| TOP
    end

    subgraph OPS[操作]
        LOCAL[owner 线程\npush/pop LIFO]
        STEAL[其他线程\npoll FIFO]
    end

    LOCAL -->|push 新任务到| TOP
    LOCAL -->|pop 取任务从| TOP
    STEAL -->|poll 窃取从| BASE

class BASE,LOCAL,STEAL,TOP condition;
class WQ data;
class E0,E1,E2,OPS process;
字段作用说明
scanState线程工作状态<0=失活(INACTIVE),奇数=正在扫描(SCANNING),偶数=忙碌
base队列头部poll() 从此处取任务(FIFO),volatile 使窃取线程可见
top队列尾部push()/pop() 在此操作(LIFO),仅 owner 线程访问,非 volatile
config索引+模式低 16 位=poolIndex,高 16 位=模式(SHARED_QUEUE/FIFO_QUEUE/LIFO_QUEUE)
qlock锁定标志基于 CAS 的轻量锁,替代 synchronized
currentSteal当前窃取任务记录当前正在执行的、从其他队列窃取来的任务,供 helpStealer 定位
currentJoin当前 join 任务记录当前线程正在等待(join)的任务,供 helpStealer 找到"窃取链"

任务数组的容量始终是 2 的幂,初始大小为 1 << 13(8192),最大为 1 << 26(约 67M)。扩容按 2 倍增长。

共享队列与私有队列的区分

队列类型索引规律config 模式owner用途
外部提交队列偶数槽位SHARED_QUEUEnull接收外部 execute()/submit() 提交的任务
工作线程队列奇数槽位LIFO_QUEUE/FIFO_QUEUE所属的 ForkJoinWorkerThread存储该线程 fork() 产生的子任务

📖 四、源码分析(一):外部任务提交流程

🔄 4.1 externalPush——提交流程入口

外部通过 execute()submit()invoke() 提交任务时,最终都落入 externalPush(task)

final void externalPush(ForkJoinTask<?> task) {
    WorkQueue[] ws; WorkQueue q; int m;
    int r = ThreadLocalRandom.getProbe();       // ① 获取当前线程的探针值
    int rs = runState;
    if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
        (q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 &&
        U.compareAndSwapInt(q, QLOCK, 0, 1)) { // ② CAS 锁定随机偶数槽位
        ForkJoinTask<?>[] a; int am, n, s;
        if ((a = q.array) != null &&
            (am = a.length - 1) > (n = (s = q.top) - q.base)) {
            int j = ((am & s) << ASHIFT) + ABASE;
            U.putOrderedObject(a, j, task);     // ③ 任务放入数组
            U.putOrderedInt(q, QTOP, s + 1);    // ④ 更新 top
            U.putOrderedInt(q, QLOCK, 0);       // ⑤ 解锁
            if (n <= 1)
                signalWork(ws, q);              // ⑥ 通知线程来执行
            return;
        }
        U.compareAndSwapInt(q, QLOCK, 1, 0);    // ⑦ 失败则回退
    }
    externalSubmit(task);                       // ⑧ 完整路径(首次或特殊场景)
}

逐行解读:

  • ① 探针值 rThreadLocalRandom.getProbe() 返回当前线程的一个随机值,用于分散提交到不同队列,避免全部命中同一个槽位。
  • ② CAS 锁定偶数槽位m & r & SQMASK 计算目标偶数索引。CAS 对 qlock 加锁,失败说明槽位被争用,回退到 externalSubmit
  • putOrderedObject:将任务放入数组的 (am & top) 位置。putOrderedObject 是一个 有序写(lazySet 语义),不保证立即可见但保证最终可见且不重排序——介于普通写和 volatile 写之间的开销。
  • putOrderedInt 更新 top:top 使用有序写而非 volatile,因为只有 owner 线程读取 top。
  • ⑤ 解锁putOrderedInt(q, QLOCK, 0) 释放锁。
  • signalWork:当队列原本为空(n <= 1),通知线程池激活一个新线程来消费该任务。
  • ⑦ 失败回退:CAS 锁定失败或队列已满时回退。
  • externalSubmit:完整的首次提交路径,处理初始化、槽位创建等逻辑。
flowchart TD
%% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %%
classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb;
classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:2px,color:#ede9fe,font-weight:bold;
classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2.5px,color:#fce7f3,font-weight:bold;
    A[externalPush task] --> B[获取线程探针值r]
    B --> C[CAS锁定随机偶数槽位]
    C --> D{锁定成功且队列空间充足?}
    D -->|是| E[putOrderedObject\n将task放入数组]
    E --> F[putOrderedInt\n更新top]
    F --> G[putOrderedInt\n解锁qlock]
    G --> H{队列原本为空?}
    H -->|是| I[signalWork\n激活工作线程]
    H -->|否| J[提交完成]
    D -->|否| K[externalSubmit\n完整提交路径]

class D,H condition;
class A,B,C,E,F,G,I,K process;
class J startEnd;

📌 4.2 signalWork——激活或创建工作线程

final void signalWork(WorkQueue[] ws, WorkQueue q) {
    long c; int sp, i; WorkQueue v; Thread p;
    while ((c = ctl) < 0L) {                        // ① AC < 0,活跃线程不足
        if ((sp = (int)c) == 0) {                   // ② 无空闲线程
            if ((c & ADD_WORKER) != 0L)             // ③ 允许添加线程
                tryAddWorker(c);                    // ④ 创建新线程
            break;
        }
        if (ws == null)
            break;
        if (ws.length <= (i = sp & SMASK))          // ⑤ 栈顶索引越界
            break;
        if ((v = ws[i]) == null)
            break;
        int vs = (sp + SS_SEQ) & ~INACTIVE;         // ⑥ 计算新scanState(加版本号)
        int d = sp - v.scanState;                   // ⑦ 版本号差值
        long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
        if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {  // ⑧ CAS 更新 ctl
            v.scanState = vs;
            if ((p = v.parker) != null)
                U.unpark(p);                        // ⑨ 唤醒阻塞线程
            break;
        }
    }
}

逐行解读:

  • ctl < 0L:判断活跃线程数是否小于并行度。循环体中的每次 CAS 失败都会重新读取 ctl 再判断。
  • sp == 0:低 16 位全 0 表示 Treiber 栈为空,没有空闲线程可以唤醒。
  • ADD_WORKER 标志ctl 的 bit 47 标识是否允许创建新线程。当总线程数未超限时此标志为 1。
  • tryAddWorker(c):CAS 更新 ctl(同时增加 AC 和 TC),然后调用 createWorker()
  • sp + SS_SEQ:递增版本号,防止 ABA 问题——同一个线程被唤醒、执行完毕、再次失活之间的状态混淆。
  • ⑧ CAS 更新 ctl:用 c + AC_UNIT 增加活跃线程计数,用 v.stackPred 更新栈顶为下一个空闲线程。
  • U.unpark(p):唤醒阻塞线程,让其从 awaitWork() 返回。

📌 4.3 createWorker——注册新的工作线程

private boolean createWorker() {
    ForkJoinWorkerThreadFactory fac = factory;
    Throwable ex = null;
    ForkJoinWorkerThread wt = null;
    try {
        if (fac != null && (wt = fac.newThread(this)) != null) {
            wt.start();                          // 启动线程 → 调用 ForkJoinWorkerThread.run()
            return true;
        }
    } catch (Throwable rex) {
        ex = rex;
    }
    deregisterWorker(wt, ex);                    // 创建失败,清理
    return false;
}

ForkJoinWorkerThread 的构造函数中调用了 pool.registerWorker(this),这个方法负责:

  1. 为线程分配一个 WorkQueue(奇数索引)
  2. 将 WorkQueue 注册到 workQueues 数组
  3. 如果数组满则扩容(2 倍)
final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
    // ...
    WorkQueue w = new WorkQueue(this, wt);
    int i = 0;
    int mode = config & MODE_MASK;
    int rs = lockRunState();
    try {
        WorkQueue[] ws; int n;
        if ((ws = workQueues) != null && (n = ws.length) > 0) {
            int s = indexSeed += SEED_INCREMENT;
            int m = n - 1;
            i = ((s << 1) | 1) & m;             // 计算奇数索引
            if (ws[i] != null) {                 // 槽位冲突
                int probes = 0;
                int step = (n <= 4) ? 2 :
                    ((n >>> 1) & EVENMASK) + 2;  // 线性探测步长
                while (ws[i = (i + step) & m] != null) {
                    if (++probes >= n) {
                        workQueues = ws = Arrays.copyOf(ws, n <<= 1);
                        m = n - 1;
                        probes = 0;
                    }
                }
            }
            // ...
            ws[i] = w;
        }
    } finally { unlockRunState(rs, rs); }
    wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1)));
    return w;
}

关键点:索引计算 ((s << 1) | 1) & m 保证 Worker 的队列始终占用 奇数槽位<< 1 左移去除符号位,| 1 保证结果为奇数。槽位冲突时使用线性探测,步长为偶数((n >>> 1) & EVENMASK) + 2),保证探测只在奇数槽位间跳跃。

📖 五、源码分析(二):工作窃取核心——scan 方法

📌 5.1 ForkJoinWorkerThread.run() 的顶层循环

public void run() {
    if (workQueue.array == null) {       // 首次运行才初始化
        Throwable exception = null;
        try {
            onStart();
            pool.runWorker(workQueue);   // 核心循环
        } catch (Throwable ex) {
            exception = ex;
        } finally {
            try { onTermination(exception); }
            catch (Throwable ex) { ... }
            finally {
                pool.deregisterWorker(this, exception);
            }
        }
    }
}

runWorker 是工作线程的核心循环:

final void runWorker(WorkQueue w) {
    w.growArray();                       // 分配任务数组
    int seed = w.hint;
    int r = (seed == 0) ? 1 : seed;
    for (ForkJoinTask<?> t;;) {
        if ((t = scan(w, r)) != null)    // ① 扫描窃取任务
            w.runTask(t);                // ② 执行窃取到的任务
        else if (!awaitWork(w, r))       // ③ 无任务,进入等待
            break;                       // ④ 超时/终止,退出
        r ^= r << 13; r ^= r >>> 17;
        r ^= r << 5;                     // ⑤ 更新随机种子
    }
}

工作线程在 scan → runTask → scan → ... → awaitWork 之间循环:

flowchart TD
%% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %%
classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2.5px,color:#fce7f3,font-weight:bold;
classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb;
classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:2px,color:#ede9fe,font-weight:bold;
    A[runWorker 开始] --> B[growArray\n分配任务数组]
    B --> C[scan 扫描窃取任务]
    C --> D{scan 成功?}
    D -->|是 有任务| E[runTask 执行任务\ndoExec + execLocalTasks]
    D -->|否 无任务| F{awaitWork 等待}
    F -->|被唤醒| C
    F -->|超时/终止| G[deregisterWorker\n线程退出]
    E -->|更新随机种子| C

class D,F condition;
class B,C,E,G process;
class A startEnd;

📌 5.2 scan()——随机窃取与失活逻辑

scan(WorkQueue w, int r) 是整个工作窃取算法的核心实现。其流程非常精妙:

private ForkJoinTask<?> scan(WorkQueue w, int r) {
    WorkQueue[] ws; int m;
    if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
        int ss = w.scanState;
        for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
            WorkQueue q; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
            int b, n; long c;
            if ((q = ws[k]) != null) {                       // ① 当前槽位有队列
                if ((n = (b = q.base) - q.top) < 0 &&        // ② 队列非空
                    (a = q.array) != null) {
                    long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
                    if ((t = ((ForkJoinTask<?>)
                              U.getObjectVolatile(a, i))) != null && // ③ volatile 读 base 位置
                        q.base == b) {
                        if (ss >= 0) {
                            if (U.compareAndSwapObject(a, i, t, null)) {  // ④ CAS 取走任务
                                q.base = b + 1;              // ⑤ 更新 base
                                if (n < -1)
                                    signalWork(ws, q);       // ⑥ 还剩任务,通知其他线程
                                return t;
                            }
                        }
                        else if (oldSum == 0 &&
                                 w.scanState < 0)
                            tryRelease(c = ctl, ws[m & (int)c], AC_UNIT); // ⑦ 唤醒失活线程
                    }
                    if (ss < 0)
                        ss = w.scanState;
                }
            }
            if (--checkSum == 0) {
                if (ss < 0) {                                 // ⑧ 当前线程已失活
                    if (oldSum == 0 && (oldSum = checkSum = 1) == 1) {
                        // ... 尝试再次扫描 (双重检查)
                        continue;
                    }
                }
                // ... oldSum/checkSum 递增逻辑,防止无限循环
            }
            if ((k = (k + 1) & m) == origin) {               // ⑨ 扫描完一整圈
                if (ss >= 0)
                    return null;
                break;
            }
        }
        // ⑩ 失活——CAS 将 scanState 设为 INACTIVE,更新 ctl
        int ns = ss | INACTIVE;
        long nc = ((SP_MASK & ns) | (UC_MASK & ((c = ctl) - AC_UNIT)));
        w.stackPred = (int) c;
        U.putInt(w, QSCANSTATE, ns);
        if (U.compareAndSwapLong(this, CTL, c, nc)) {
            ss = ns;
            // ... 更新 w.hint
        }
        // ⑪ 灭活后再次扫描一圈
        // ... 如果仍然无任务,返回 null(最终进入 awaitWork)
    }
    return null;
}

这段代码的流程可总结为以下步骤:

flowchart TD
%% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %%
classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2.5px,color:#fce7f3,font-weight:bold;
classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:2px,color:#ede9fe,font-weight:bold;
classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb;
    A[scan入口] --> B[从随机起点k=origin开始]
    B --> C{当前槽位k有队列\n且队列非空?}
    C -->|是| D[volatile读base位置的任务]
    D --> E{任务不为null\n且base未变?}
    E -->|是| F{当前线程活跃ss>=0?}
    F -->|是| G[CAS从base取走任务\n更新q.base=b+1]
    G --> H{队列还剩任务?}
    H -->|是| I[signalWork 通知其他线程]
    H -->|否| J[返回任务]
    I --> J
    F -->|否-线程已失活| K{oldSum==0?}
    K -->|是| L[tryRelease\n唤醒栈顶线程]
    E -->|否| M[检查checkSum\n防止无限循环]
    L --> M
    K -->|否| M
    C -->|否| M
    M -->|k=k+1| N{k==origin\n已扫描一圈?}
    N -->|否| C
    N -->|是| O{线程活跃ss>=0?}
    O -->|是| P[return null\n无任务可窃取]
    O -->|否-已失活| Q[设置INACTIVE\nCAS更新ctl减少AC]
    Q --> R[灭活后再次扫描一圈\n双圈检查]
    R -->|仍无任务| S[return null\n进入awaitWork]

class C,E,F,H,K,M,N,O,R condition;
class D,G,I,L,Q process;
class A,B,J,P,S startEnd;

双圈检查的设计意图:线程灭活(设为 INACTIVE)后会再次扫描一整圈。这是因为在设置 INACTIVE 的 CAS 前后,可能有新的任务被提交——双圈扫描作为第二道防线,避免线程过早进入阻塞。

📌 5.3 tryRelease——唤醒空闲线程

private boolean tryRelease(long c, WorkQueue v, long inc) {
    int sp = (int) c, vs = (sp + SS_SEQ) & ~INACTIVE;
    if (v != null && v.scanState == sp) {
        long nc = (UC_MASK & (c + inc)) | (SP_MASK & v.stackPred);
        if (U.compareAndSwapLong(this, CTL, c, nc)) {
            v.scanState = vs;
            if ((p = v.parker) != null)
                U.unpark(p);
            return true;
        }
    }
    return false;
}

tryRelease 被两种场景调用:

  1. scan 中发现任务但活跃线程不足inc = AC_UNIT):增加 AC 计数后唤醒栈顶空闲线程
  2. deregisterWorker 中线程退出inc = TC_UNIT + AC_UNIT):减少 TC 和 AC,唤醒替换线程

📖 六、源码分析(三):fork 与 join——子任务的生与合

📌 6.1 fork()——子任务入队

public final ForkJoinTask<V> fork() {
    Thread t;
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);  // ① Worker 线程:push 到自身队列
    else
        ForkJoinPool.common.externalPush(this);           // ② 外部线程:走 externalPush
    return this;
}

工作线程执行子任务 fork() 时,任务被 push 到自身 WorkQueue 的 top 端(LIFO):

final void push(ForkJoinTask<?> task) {
    ForkJoinTask<?>[] a; ForkJoinPool p;
    int b = base, s = top, n;
    if ((a = array) != null) {
        int m = a.length - 1;
        U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);  // ① 放入 (m & top) 位置
        U.putOrderedInt(this, QTOP, s + 1);                        // ② top + 1
        if ((n = s - b) <= 1) {
            if ((p = pool) != null)
                p.signalWork(p.workQueues, this);                  // ③ 队列原为空,通知
        }
        else if (n >= m)
            growArray();                                           // ④ 满则扩容
    }
}

📌 6.2 join()——等待子任务结果

join() 的源码极其简洁,核心逻辑在 doJoin() 中:

public final V join() {
    int s;
    if ((s = doJoin() & DONE_MASK) != NORMAL)
        reportException(s);
    return getRawResult();
}

private int doJoin() {
    int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
    return (s = status) < 0 ? s :    // ① 检查是否已经完成
        ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
        (w = (wt = (ForkJoinWorkerThread)t).workQueue).
        tryUnpush(this) && (s = doExec()) < 0 ? s :  // ② 尝试从本地 top 弹出并执行
        wt.pool.awaitJoin(w, this, 0L) :             // ③ 否则进入等待
        externalAwaitDone();                          // ④ 外部线程的等待路径
}

doJoin() 的三步策略是 ForkJoinPool 性能优化最精华的部分

优先级策略条件效果
1status < 0 快速返回任务已完成零开销
2tryUnpush 弹出并直接执行任务在本地队列 top 端避免阻塞,最佳路径
3awaitJoin 进入等待任务不在本地或不在 top帮助执行其他任务,必要时阻塞

为什么 tryUnpush 是最佳路径? 子任务刚 fork() 后被 push 到 top 端,紧接着调用 join() 时它大概率还在 top 位置。此时直接弹出执行,避免了上下文切换和任务在队列间的转移。

📐 6.3 awaitJoin——加入等待的三种策略

final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
    int s = 0;
    if (task != w.currentJoin)
        w.currentJoin = task;                        // ① 记录当前 join 的任务
    ForkJoinTask<?> prevJoin = w.currentJoin;        //    供 helpStealer 定位
    for (int k = 0, side = 0;;) {
        if ((s = task.status) < 0)                   // ② 任务已完成
            break;
        if (side == 0) {
            ForkJoinTask<?> subtask = w.currentSteal; // ③ 当前窃取的任务
            if (subtask != null)
                ForkJoinTask.helpExpungeStaleExceptions();
            if (subtask instanceof CountedCompleter)
                helpComplete(w, (CountedCompleter<?>) subtask, k, side);
            else if (w.base == w.top ||              // ④ 本地队列为空
                     w.tryRemoveAndExec(task))       // ⑤ 尝试找到并执行
                helpStealer(w, task, k);             // ⑥ 核心:帮助窃取者执行
        }
        // ... 更多侧循环逻辑
        if ((s = task.status) < 0)                   // ⑦ 再次检查
            break;
        // ⑧ tryCompensate:补偿机制
        Thread.interrupted();
        if (deadline == 0L)
            ForkJoinPool.tryCompensate(w);
        // ⑨ 内部等待(自旋 + timed park)
        long ms = 1L;
        task.internalWait(ms);
    }
    w.currentJoin = prevJoin;
    return s;
}

awaitJoin 在等待任务完成时,不是简单地阻塞,而是主动帮助其他线程完成工作。这是 ForkJoinPool 区别于 ThreadPoolExecutor 最核心的特征。

▶️ 6.4 helpStealer——帮助窃取者执行

这是工作窃取中"任务转让"最精妙的部分。当线程 A 的 join() 阻塞在某个任务 T 上时,T 可能被线程 B 窃取并正在执行。线程 A 的逻辑是:找到窃取了 T 的线程 B,去帮 B 执行它的任务,从而间接加速 T 的完成。

final void helpStealer(WorkQueue w, ForkJoinTask<?> task, int k) {
    WorkQueue[] ws = workQueues;
    int n = ws.length;
    if (ws != null && n > 0) {
        for (int m = n - 1, j = -n - m;;) {          // ① 从后往前扫描
            WorkQueue q = ws[j & m & 0xffff];
            if (q != null) {
                ForkJoinTask<?> t = q.currentSteal;   // ② 获取该队列当前执行的窃取任务
                if (t == task) {                       // ③ 找到窃取者
                    if (w.tryRemoveAndExec(task))      // ④ 尝试执行目标任务
                        break;
                    do {
                        if ((t = q.currentSteal) == task) {
                            if (w.base - w.top >= 0)
                                return;
                            do {} while (!w.tryRemoveAndExec(task));
                        }
                        // ⑤ 从窃取者队列 poll 任务执行
                        while (q.base != q.top) {
                            ForkJoinTask<?> o = q.poll();
                            if (o != null) {
                                w.runSubtask(o);       // ⑥ 帮助执行窃取者的任务
                            }
                        }
                    } while (w.base - w.top < 0);      // ⑦ 本地有新任务产生
                    break;
                }
            }
            if (--j == 0)
                break;
        }
    }
}
sequenceDiagram
    participant A as 线程A\n本地队列[A1,A2]
    participant B as 线程B\n本地队列[B1,B2,B3]
    participant T as ForkJoinTask T

    Note over A: join(T) 阻塞
    A->>B: ① 查找谁窃取了 T
    A->>B: ② q.currentSteal == T\n找到了!B 正在执行 T
    A->>B: ③ 帮助 B poll 任务执行\nB1, B2 被 A 执行
    A->>A: ④ 本地队列新产生的任务\nA3, A4 也被处理
    B->>T: B 继续执行 T
    Note over A: T 完成后被唤醒
    A->>T: ⑤ task.status < 0\njoin 返回

这就是 fork/join 框架最核心的"任务在不同线程间转让"机制:线程 A 在等待自己的任务 T 时,不是空转或阻塞,而是去帮助"阻碍 T 完成"的线程 B 执行它的任务。A 帮 B,B 就能更快完成 T,A 也就能更快从 join() 返回。

📌 6.5 tryCompensate——补偿线程创建

当所有工作线程都忙且等待链太长时,需要创建补偿线程(补偿线程,Compensation Thread)来打破僵局:

final 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 & 0x7fffffff], TC_UNIT); // ① 有空闲线程,唤醒
    else {
        int ac = (int)(c >> AC_SHIFT) + pc;
        int tc = (short)(c >> TC_SHIFT) + pc;
        int nbusy = 0;
        for (int i = 0; i <= m; ++i) {
            WorkQueue v = ws[i];
            if (v != null && v.scanState >= 0) ++nbusy;        // ② 统计忙碌线程
        }
        if (nbusy != (tc + ac) || c != ctl)
            canBlock = false;
        else if (tc >= pc && ac > 1 && w.isEmpty())
            canBlock = false;                                   // ③ 不需要补偿
        else
            canBlock = tryAddWorker(c);                         // ④ 创建补偿线程
    }
    return canBlock;
}

补偿决策树:

条件决策原因
有空闲线程(sp != 0唤醒空闲线程直接唤醒即可,无需创建
tc >= pc && ac > 1 && 队列空不补偿线程数已达并行度,且调用者无本地任务
总线程 < 并行度 或 活跃线程 ≤ 1创建补偿线程避免死锁:当所有线程都在 join 等待时,需要额外线程来执行就绪任务

📖 七、源码分析(四):任务执行——runTask 与本地任务消费

▶️ 7.1 runTask——执行窃取到的任务

final void runTask(ForkJoinTask<?> task) {
    if (task != null) {
        scanState &= ~SCANNING;                    // ① 清除 SCANNING,标记忙碌
        (currentSteal = task).doExec();            // ② 执行窃取任务
        U.putOrderedObject(this, QCURRENTSTEAL, null);
        execLocalTasks();                          // ③ 消费本地队列中的所有任务
        ForkJoinTask<?>[] a = array;
        if (a != null && a.length > 0)
            reinitialize();                        // ④ 重置 WorkQueue 内部状态
        if (++nsteals < 0)
            transferStealCount(pool);              // ⑤ 窃取计数溢出处理
        scanState |= SCANNING;                     // ⑥ 恢复 SCANNING 状态
        if (owner != null)
            owner.afterTopLevelExec();             // ⑦ 顶层任务执行后的钩子
    }
}

关键设计:窃取一个任务后,顺带消费本地队列中的所有任务(execLocalTasks。这是 LIFO 策略的优势所在——从窃取任务中 fork 出的子任务都在本地队列中,批量执行可最大化缓存局部性。

📌 7.2 execLocalTasks——批量消费本地任务

final void execLocalTasks() {
    int b = base, m, s;
    ForkJoinTask<?>[] a = array;
    if (b - (s = top - 1) <= 0 && a != null && (m = a.length - 1) >= 0) {
        if ((config & FIFO_QUEUE) == 0) {          // LIFO 模式(默认)
            for (ForkJoinTask<?> t;;) {
                if ((t = (ForkJoinTask<?>)
                     U.getObject(a, ((m & s) << ASHIFT) + ABASE)) == null)
                    break;
                U.putOrderedInt(this, QTOP, s);
                t.doExec();
                if (base - (s = top - 1) > 0)
                    break;                         // 队列为空则退出
            }
        }
        else                                       // FIFO 模式
            pollAndExecAll();                      // 从 base 端取,类似窃取的顺序
    }
}

LIFO 模式下,从 top 端逐个 pop 并执行,每个任务的执行都可能产生新子任务 push 到 top 端——形成深度优先的计算模式。FIFO 模式下则从 base 端取,产生广度优先的计算模式。

🛠️ 八、日常使用方式

📌 8.1 创建 ForkJoinPool

// 方式一:自定义并行度
ForkJoinPool pool = new ForkJoinPool(4);  // 4 个工作线程

// 方式二:完整参数
ForkJoinPool pool = new ForkJoinPool(
    4,                    // parallelism:并行度
    ForkJoinPool.defaultForkJoinWorkerThreadFactory,
    null,                 // handler:异常处理器
    false                 // asyncMode:false=LIFO(默认),true=FIFO
);

// 方式三:使用公共池(JDK 8+)
ForkJoinPool common = ForkJoinPool.commonPool();
// 默认并行度 = Runtime.getRuntime().availableProcessors() - 1

// 方式四:通过 Executors
ExecutorService pool = Executors.newWorkStealingPool();
// 等同于 new ForkJoinPool(parallelism, factory, handler, true)

📌 8.2 高频 API

方法用途频率
pool.invoke(task)提交 ForkJoinTask 并阻塞等待结果
pool.submit(task)提交任务,返回 Future,不等待
task.fork()子任务异步提交到本地队列
task.join()阻塞等待子任务完成并返回结果
task.compute()当前线程直接执行
RecursiveTask.invokeAll(t1, t2)并行执行两个子任务(推荐方式)
pool.execute(task)提交任务,无返回值
task.quietlyJoin()静默等待(不抛异常)
task.isCompletedAbnormally()检查是否异常完成

📌 8.3 RecursiveTask 有返回值任务

public class ArraySum {
    static class SumTask extends RecursiveTask<Long> {
        private final int[] arr;
        private final int start, end;
        static final int THRESHOLD = 1000;

        SumTask(int[] arr, int start, int end) {
            this.arr = arr;
            this.start = start;
            this.end = end;
        }

        @Override
        protected Long compute() {
            if (end - start <= THRESHOLD) {
                long sum = 0;
                for (int i = start; i < end; i++) sum += arr[i];
                return sum;
            }
            int mid = (start + end) >>> 1;
            SumTask left = new SumTask(arr, start, mid);
            SumTask right = new SumTask(arr, mid, end);
            invokeAll(left, right);   // 推荐:框架自动优化并行
            return left.join() + right.join();
        }
    }

    public static void main(String[] args) {
        int[] arr = new int[10_000_000];
        Arrays.parallelSetAll(arr, i -> i);  // 并行填充
        ForkJoinPool pool = new ForkJoinPool();
        long result = pool.invoke(new SumTask(arr, 0, arr.length));
        System.out.println("Sum: " + result);
    }
}

📌 8.4 RecursiveAction 无返回值任务

public class QuickSortParallel {
    static class SortTask extends RecursiveAction {
        private final int[] arr;
        private final int lo, hi;
        static final int THRESHOLD = 1000;

        SortTask(int[] arr, int lo, int hi) {
            this.arr = arr;
            this.lo = lo;
            this.hi = hi;
        }

        @Override
        protected void compute() {
            if (hi - lo <= THRESHOLD) {
                Arrays.sort(arr, lo, hi);
                return;
            }
            int pivot = partition(arr, lo, hi);
            SortTask left = new SortTask(arr, lo, pivot);
            SortTask right = new SortTask(arr, pivot + 1, hi);
            invokeAll(left, right);
        }

        private int partition(int[] arr, int lo, int hi) {
            // 标准快排分区逻辑
            int pivot = arr[lo];
            int i = lo, j = hi;
            while (i < j) {
                while (i < j && arr[--j] >= pivot);
                arr[i] = arr[j];
                while (i < j && arr[++i] <= pivot);
                arr[j] = arr[i];
            }
            arr[i] = pivot;
            return i;
        }
    }
}

📌 8.5 JDK 中 ForkJoinPool 的实际使用

JDK 组件使用 ForkJoinPool说明
Arrays.parallelSort()使用 ForkJoinPool.commonPool()并行排序,JDK 8+
ConcurrentHashMap.forEach()使用 ForkJoinPool.commonPool()并行遍历,JDK 8+
CompletableFuture.supplyAsync()默认使用 ForkJoinPool.commonPool()异步任务的默认线程池
parallel() Stream使用 ForkJoinPool.commonPool()并行流的底层执行器

九、与 ThreadPoolExecutor 的本质区别

flowchart TD
%% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %%
classDef data fill:#052e16,stroke:#16a34a,stroke-width:2px,color:#bbf7d0,font-weight:bold;
classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb;
    subgraph TPE[ThreadPoolExecutor]
        direction TD
        SHARED[单一共享阻塞队列\nBlockingQueue]
        T1[线程1]
        T2[线程2]
        T3[线程3]
        SHARED -->|竞争poll| T1
        SHARED -->|竞争poll| T2
        SHARED -->|竞争poll| T3
    end

    subgraph FJP[ForkJoinPool]
        direction TD
        T4Q[线程1\nWorkQueue 双端队列]
        T5Q[线程2\nWorkQueue 双端队列]
        T6Q[线程3\nWorkQueue 双端队列]
        T4 --> T4Q
        T5 --> T5Q
        T6 --> T6Q
        T4Q -.->|空闲时窃取| T5Q
        T5Q -.->|空闲时窃取| T6Q
        T6Q -.->|空闲时窃取| T4Q
    end

class FJP,SHARED,T4Q,T5Q,T6Q,TPE data;
class T1,T2,T3 process;
维度ForkJoinPoolThreadPoolExecutor
队列结构每个线程独立 WorkQueue(双端队列)单一共享 BlockingQueue
任务调度工作窃取(Work-Stealing)生产者-消费者模型
负载均衡空闲线程主动从其他队列窃取所有线程争抢同一队列
任务类型专为 ForkJoinTask 设计(支持 fork/join)通用 Runnable / Callable
任务产生方式任务内部可 fork() 产生子任务所有任务由外部提交
本地执行LIFO 从 top 端取,缓存友好无本地队列概念
等待策略awaitJoin → helpStealer 帮助执行park 阻塞等待
线程数控制并行度(parallelism),可创建补偿线程corePoolSize + maxPoolSize
空闲收缩2 秒超时无任务则退出keepAliveTime 可配置
防伪共享@Contended 注解 WorkQueue

最重要的区别是:ForkJoinPool 的线程在"等待"时从不空闲——它们会走到 helpStealer 路径,主动帮助其他线程执行任务。这使得 ForkJoinPool 在处理递归分治任务时的 CPU 利用率远高于 ThreadPoolExecutor。

🛠️ 十、使用注意事项

📌 10.1 避免不必要的 fork

// 错误:过度 fork
left.fork();
right.fork();
long result = left.join() + right.join();  // 两个 join 都阻塞

// 正确:一个当前线程执行,一个 fork
left.fork();
long rightResult = right.compute();        // 当前线程直接计算
long leftResult = left.join();             // 等待异步的那个
return leftResult + rightResult;

// 最佳:使用 invokeAll
invokeAll(left, right);
return left.join() + right.join();

📌 10.2 正确设置阈值

阈值(THRESHOLD)决定了任务的粒度。过大则并行度不足,过小则 fork/join 开销大于计算本身。

建议说明
单次 compute() 应执行 100 ~ 10000 个基本计算步骤Doug Lea 的推荐范围
通过 JMH 实际测试确定最优阈值不同硬件/JDK 版本有差异
测试前需要 JIT 预热确保编译优化已生效

📌 10.3 异常处理:子任务异常不会自动传播

ForkJoinTask 的异常处理机制与普通 Future 不同:

SumTask task = new SumTask(arr, 0, arr.length);
pool.execute(task);  // 用 execute 而非 invoke
// task 内部的异常被捕获并保存在 status 中
// 必须显式调用 join()/get() 才会抛出异常
try {
    task.join();
} catch (RuntimeException e) {
    // 处理异常
}

可以用 isCompletedAbnormally() 检查任务是否异常完成:

if (task.isCompletedAbnormally()) {
    // 通过 getException() 获取原始异常
    Throwable ex = task.getException();
}

📌 10.4 避免在 ForkJoinTask 中使用阻塞 I/O

ForkJoinPool 是为 CPU 密集型计算设计的。如果在 compute() 中执行 I/O 阻塞操作,会占用工作线程且无法被窃取。如果需要混用 CPU 计算和 I/O,应考虑:

  • 使用 ManagedBlocker 接口通知线程池当前线程即将阻塞
  • 或者将 CPU 任务和 I/O 任务分离到不同的线程池

📌 10.5 Common Pool 的注意事项

ForkJoinPool.commonPool() 是 JVM 级别的共享线程池,parallelStream()CompletableFuture 等都默认使用它:

// 错误:在 commonPool 中执行耗时任务会影响全局
IntStream.range(0, 100).parallel().forEach(i -> {
    Thread.sleep(10000);  // 阻塞 commonPool 线程,影响其他所有使用它的功能
});

// 正确:耗时/阻塞任务使用自定义线程池
ForkJoinPool customPool = new ForkJoinPool(4);
customPool.submit(() -> {
    // 耗时操作
}).get();

🎯 10.6 适用场景总结

场景适合说明
大量小规模计算任务(递归拆分)核心场景,fork/join 天然高效
并行排序、并行数组操作JDK 内置 parallelSort
大多数任务是独立、无fork/join关系的用 ThreadPoolExecutor 更简单
涉及阻塞 I/O 的任务CPU 密集设计,阻塞会耗尽线程
任务粒度极细(微秒级)fork/join 开销可能大于计算本身
需要任务优先级调度工作窃取不提供优先级支持

十一、总结

ForkJoinPool 通过 分治递归 + 工作窃取 + 双端队列 + helpStealer 任务转让 四层机制,构建了一个专为递归并行计算优化的线程池。

flowchart TD
%% 半暗底色 + 高亮描边:完美适配博客深色/浅色双主题 %%
classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb;
classDef data fill:#052e16,stroke:#16a34a,stroke-width:2px,color:#bbf7d0,font-weight:bold;
classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:2px,color:#ede9fe,font-weight:bold;
classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2.5px,color:#fce7f3,font-weight:bold;
classDef highlight fill:#431407,stroke:#ea580c,stroke-width:2px,color:#fed7aa,font-weight:bold;
    subgraph DESIGN[设计思想]
        A[分治算法\n递归拆分→求解→合并]
        B[工作窃取\n空闲线程主动\n从其他队列poll任务]
    end

    subgraph STRUCT[核心数据结构]
        C[ctl 64位控制字\nAC/TC/SS/ID四域合一]
        D[WorkQueue 双端队列\nbase端FIFO窃取\ntop端LIFO本地]
        E[ForkJoinTask status\n高4位完成状态\n低位SIGNAL信号]
    end

    subgraph FLOW[核心流程]
        F[externalPush\n提交到偶数槽位]
        G[scan 扫描窃取\n随机起点→轮询→双圈检查]
        H[runTask\ndoExec + execLocalTasks]
        I[awaitJoin\nhelpStealer帮助执行\ntryCompensate补偿线程]
    end

    DESIGN --> STRUCT
    STRUCT --> FLOW

class D,G condition;
class B,STRUCT data;
class FLOW highlight;
class A,C,DESIGN,F,H,I process;
class E startEnd;
维度要点回顾
设计思想分治算法(拆分→求解→合并)+ 工作窃取(空闲线程主动 poll 其他队列)
核心数据结构ctl(64 位 AC/TC/SS/ID)+ WorkQueue(base/top 双端)+ ForkJoinTask(status 状态机)
外部提交externalPush → 随机偶数槽位 → CAS 锁定 → signalWork 激活线程
工作窃取scan 方法:随机起点轮询每个槽位 → CAS poll base 端 → 双圈无任务则灭活进入 awaitWork
forkpush 到 top 端(LIFO),队列由空变非空时调用 signalWork
jointryUnpush(top 位直接执行)→ awaitJoinhelpStealer(帮窃取者执行)→ tryCompensate(补偿线程)
空闲唤醒tryRelease:加版本号防 ABA → CAS 更新 ctl → unpark
线程退出deregisterWorker:移除 WorkQueue → 转移 stealCount → 取消剩余任务 → tryAddWorker 替换
与 TPE 区别多独立双端队列 vs 单一共享队列;工作窃取 vs 生产者-消费者;等待时帮助执行 vs 纯阻塞