ForkJoinPool 源码

ForkJoinTask

任务分类

  1. ForkJoinTask 抽象任务基类
  2. RecursiveTask 继承 ForkJoinTask 有返回值递归任务
  3. RecursiveAction 继承 ForkJoinTask 无返回值递归任务

fork

  1. 第一次 fork:调用线程执行 compute → currentThread() = main,非 worker,调用池 externalPush
  2. 递归内层 fork:worker 线程执行 compute → 直接 push 到自身专属 WorkQueue(本地队列)
java 复制代码
public final ForkJoinTask<V> fork() {
    Thread t;
    // 判断当前执行线程是不是ForkJoinWorkerThread
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
        ForkJoinPool.common.externalPush(this);
    return this;
}

join

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

doJoin

java 复制代码
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 :
        wt.pool.awaitJoin(w, this, 0L) :
        externalAwaitDone();
}
plaintext 复制代码
条件1:任务已完成 status < 0 (< 0 说明任务运行完成)→ 直接返回状态s
    不满足条件1 → 判断条件2:当前线程是否为ForkJoinWorkerThread
         条件2成立(Worker线程)→ 执行内层逻辑:tryUnpush + doExec
             内层成功 → 返回 s
             内层失败 → pool.awaitJoin
         条件2不成立(普通线程)→ externalAwaitDone() 阻塞等待

tryUnpush

  • 将 task 从队列中移出
java 复制代码
final boolean tryUnpush(ForkJoinTask<?> t) {
    ForkJoinTask<?>[] a; int s;
    if ((a = array) != null && (s = top) != base &&
        U.compareAndSwapObject
        (a, (((a.length - 1) & --s) << ASHIFT) + ABASE, t, null)) {
        U.putOrderedInt(this, QTOP, s);
        return true;
    }
    return false;
}

internalWait

java 复制代码
final void internalWait(long timeout) {
    int s;
    if ((s = status) >= 0 && // force completer to issue notify
        U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
        synchronized (this) {
            if (status >= 0)
                try { wait(timeout); } catch (InterruptedException ie) { }
            else
                notifyAll();
        }
    }
}

WorkQueue

top:队头,当前 worker 自己取任务 LIFO(后进先出)

base:队尾,其他 worker 窃取任务从 base 拿 FIFO

自己线程:pop 从 top 取,无竞争

其他线程:steal 从 base 取,最小化竞争,高性能

成员变量

java 复制代码
// WorkQueue 核心成员变量(volatile 保证可见性)
volatile int base;           // 窃取端队尾,其他worker从此取任务
volatile int top;            // 本地写入端队头,当前worker写/取
ForkJoinTask<?>[] array;     // 环形任务数组
final ForkJoinPool pool;     // 所属线程池

// WorkQueue 字段偏移:top、base
static final long QTOP;
static final long QBASE;

构造方法

java 复制代码
WorkQueue(ForkJoinPool pool, ForkJoinWorkerThread owner) {
    this.pool = pool;
    this.owner = owner;
    base = top = INITIAL_QUEUE_CAPACITY >>> 1;
}

push

java 复制代码
final void push(ForkJoinTask<?> task) {
	// a:缓存当前队列环形数组;
	// p:缓存当前队列归属的 ForkJoinPool。
    ForkJoinTask<?>[] a; ForkJoinPool p;
    int b = base, s = top, n;
    // 如果队列被销毁(array=null),直接放弃入队,忽略本次 push
    if ((a = array) != null) {    
    	// ForkJoin 的任务数组长度永远是 2 的幂,m = 长度 - 1 作为掩码,用于 s & m 快速取模
        int m = a.length - 1;     
        // 写入任务到环形数组对应下标,视作 a[((m & s) << ASHIFT) + ABASE] = task
        // 不能替换为普通数组赋值:普通赋值缺少内存屏障,窃取线程可能看不到新任务,并发丢失任务
        // putOrderedObject 保证写入顺序不重排,后续读取线程一定能看到本次写入的 task
        U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
			
		// 视作 top++
		// 必须在任务写入完成后更新 top:其他线程读取 top 时,一定能读到已写入完成的任务
        U.putOrderedInt(this, QTOP, s + 1);
		
		// 计算当前队列现存任务数量(注意:计算的是 push 前的数量),s 为队首下标,b 为队尾下标
		//  <= 1:队列只有 0 或 1 个任务,说明当前池空闲线程不足,需要唤醒阻塞 worker 来偷任务
        if ((n = s - b) <= 1) {
            if ((p = pool) != null)
            	// 遍历队列,唤醒阻塞等待任务的空闲 ForkJoinWorkerThread,让线程来窃取本队列刚新增的任务,提升并行度
            	// 为什么 n <= 1 会调用该方法?因为 2 个以内任务属于 "稀缺任务",必须主动调度线程;超过 2 个属于批量任务,被动窃取即可
            	// 依靠空闲线程随机扫描很难被窃取,存在并行算力浪费;因此主动调用 signalWork,唤醒休眠 worker 或新建线程,保证新提交的任务立刻被并行处理
                p.signalWork(p.workQueues, this);
        }
        // 队列任务数量 n >= 数组最大容量 m
        else if (n >= m)
        	// 扩容方法,新建双倍长度(仍为 2 次幂)的 ForkJoinTask 数组,复制旧数组所有任务,替换 array 成员
            growArray();
    }
}

growArray

  • 队列数组容量翻倍扩容,迁移旧数组所有有效任务到新数组,返回新数组

扩容条件:

  1. 旧数组不为空(不是首次初始化)
  2. 旧数组长度至少为 1,保证 length - 1 合法
  3. top - base > 0:队列存在有效任务(有任务要迁移)
java 复制代码
final ForkJoinTask<?>[] growArray() {
    ForkJoinTask<?>[] oldA = array;
    int size = oldA != null ? oldA.length << 1 : INITIAL_QUEUE_CAPACITY;
    if (size > MAXIMUM_QUEUE_CAPACITY)
        throw new RejectedExecutionException("Queue capacity exceeded");
    int oldMask, t, b;
    ForkJoinTask<?>[] a = array = new ForkJoinTask<?>[size];
    // base ,队列尾部的逻辑指针,是无限递增的数字,不是数组真实下标
    if (oldA != null && (oldMask = oldA.length - 1) >= 0 &&
        (t = top) - (b = base) > 0) {
        int mask = size - 1;
        do {
            ForkJoinTask<?> x;
            // 获取旧数组 old[b] 的内存地址
            int oldj = ((b & oldMask) << ASHIFT) + ABASE;
            // 获取新数组 a[b] 的内存地址
            int j    = ((b &    mask) << ASHIFT) + ABASE;
            // 拿到旧数组对应位置任务 x
            x = (ForkJoinTask<?>)U.getObjectVolatile(oldA, oldj);

			// 判断 x 不为空:该位置存在有效任务
			// CAS 把旧数组该位置置为 null
			// 成功:代表当前线程独占拿到这个任务
			// 失败:其他偷任务线程已经拿走该任务,跳过
            if (x != null && U.compareAndSwapObject(oldA, oldj, x, null))
                // 把任务写入新数组对应位置
                U.putObjectVolatile(a, j, x);
        } while (++b != t);
    }
    return a;
}

poll

  • poll 是其他工作线程偷任务的方法
java 复制代码
final ForkJoinTask<?> poll() {
    ForkJoinTask<?>[] a; int b; ForkJoinTask<?> t;
    // base - top < 0 → base < top,队列存在有效任务
    while ((b = base) - top < 0 && (a = array) != null) {
    	// 获取 base 的内存地址
        int j = (((a.length - 1) & b) << ASHIFT) + ABASE;
        // 获取 a[j] 数据
        t = (ForkJoinTask<?>)U.getObjectVolatile(a, j);
        // 如果此刻 base != b,说明别的偷任务线程已经抢先把 base+1 拿走任务,当前线程本次竞争失败,直接下一轮 while 重试
        if (base == b) {
            if (t != null) {
            	// 将 a[j] 置为 null
                if (U.compareAndSwapObject(a, j, t, null)) {
                    base = b + 1;
                    return t;
                }
            }
            // t == null,判断队列是否彻底空了
            // b + 1 == top 说明队列除当前任务外无其他剩余任务
            else if (b + 1 == top)
                break;
        }
    }
    return null;
}

pop

  • 只有队列归属的当前 Worker 线程自己调用,用来从队头 top 取任务执行
java 复制代码
final ForkJoinTask<?> pop() {
    ForkJoinTask<?>[] a; ForkJoinTask<?> t; int m;
    // 判断数组非空
    if ((a = array) != null && (m = a.length - 1) >= 0) {
    	// 判断数组还有任务,s - base >= 0 → s >= base → top - 1 >= base 说明除当前任务外还有其他任务
        for (int s; (s = top - 1) - base >= 0;) {
        	// 获取数据下标
            long j = ((m & s) << ASHIFT) + ABASE;
            // 获取 a[j] 数据,这里只可能是单线程操作,所以不需要 volatile
            if ((t = (ForkJoinTask<?>)U.getObject(a, j)) == null)
                break;
            // a[j] 置为 null 并且修改 top = top - 1
            if (U.compareAndSwapObject(a, j, t, null)) {
                U.putOrderedInt(this, QTOP, s);
                return t;
            }
        }
    }
    return null;
}

runTask

  • 执行窃取的任务
  • 执行完成后,执行本队列的剩余任务
java 复制代码
final void runTask(ForkJoinTask<?> task) {
    if (task != null) {
        // 标记当前队列处于忙碌执行任务状态
        scanState &= ~SCANNING;
        (currentSteal = task).doExec();
        U.putOrderedObject(this, QCURRENTSTEAL, null);
        // 执行当前队列本地剩余任务
        execLocalTasks();
        ForkJoinWorkerThread thread = owner;
        if (++nsteals < 0)      // collect on overflow
            transferStealCount(pool);
        scanState |= SCANNING;
        if (thread != null)
            thread.afterTopLevelExec();
    }
}

ForkJoinPool

使用方法

java 复制代码
// 求和任务
class SumTask extends RecursiveTask<Long> {
    private long[] arr;
    private int left;
    private int right;
    // 拆分阈值
    private static final int THRESHOLD = 1000;

    public SumTask(long[] arr, int left, int right) {
        this.arr = arr;
        this.left = left;
        this.right = right;
    }

    @Override
    protected Long compute() {
        // 区间长度小于阈值:直接计算
        if (right - left <= THRESHOLD) {
            long sum = 0;
            for (int i = left; i <= right; i++) {
                sum += arr[i];
            }
            return sum;
        }
        // 二分拆分
        int mid = (left + right) / 2;
        SumTask leftTask = new SumTask(arr, left, mid);
        SumTask rightTask = new SumTask(arr, mid + 1, right);

        // 先fork,再join(标准写法)
        leftTask.fork();
        rightTask.fork();

        // 合并左右结果
        return leftTask.join() + rightTask.join();
    }
}

public class ForkJoinDemo {
    public static void main(String[] args) {
        // 构造10万测试数组
        long[] arr = new long[100000];
        long singleSum = 0;
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
            singleSum += i;
        }

        // 创建ForkJoin池执行
        ForkJoinPool pool = new ForkJoinPool();
        SumTask task = new SumTask(arr, 0, arr.length - 1);
        long forkSum = pool.invoke(task); // 阻塞获取最终结果

        System.out.println("单线程求和:" + singleSum);
        System.out.println("ForkJoin并行求和:" + forkSum);
        pool.shutdown();
    }
}

成员变量

java 复制代码
private static final long AC_SHIFT = 48L;
private static final long STOP_BIT = 1L << 63; // 停止标记
private volatile long ctl; // ctl 状态控制长整数,高16位:活跃线程数;低16位:运行状态
WorkQueue[] workQueues; // 线程专属双端队列数组,偷任务的核心载体
final int parallelism; // 并行度,默认CPU核心数
final boolean asyncMode; // FIFO/ LIFO模式,普通任务LIFO,定时任务FIFO

构造方法

java 复制代码
// 无参构造
public ForkJoinPool() {
    this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
            defaultForkJoinWorkerThreadFactory,
            null, false);
}

// 完整 4 参构造器,核心初始化逻辑
public ForkJoinPool(int parallelism,
                    ForkJoinWorkerThreadFactory factory,
                    UncaughtExceptionHandler handler,
                    boolean asyncMode) {
    checkParallelism(parallelism);
    if (factory == null)
        throw new NullPointerException();
    this.parallelism = parallelism;
    this.factory = factory;
    this.ueh = handler;
    this.asyncMode = asyncMode;
    // 初始化任务队列数组,每个工作线程对应一个双端队列WorkQueue
    this.workQueues = new WorkQueue[parallelism << 1];
    // 控制池状态:运行/关闭
    this.ctl = ((long)parallelism) << AC_SHIFT;
    // 阻塞线程计数、同步器参数初始化
    this.stackPred = asyncMode ? 0 : 1;
}

invoke

java 复制代码
public <T> T invoke(ForkJoinTask<T> task) {
    if (task == null)
        throw new NullPointerException();
    externalPush(task); // 外部线程(main主线程)提交任务走externalPush
    return task.join(); // 阻塞等待任务完成,获取结果
}

externalPush

  1. 通过随机 probe 哈希映射到一个共享 WorkQueue
  2. 外部任务存入队列头部,供 worker 线程窃取
java 复制代码
final void externalPush(ForkJoinTask<?> task) {
    WorkQueue[] ws; WorkQueue q; int m;
    // 哈希路由到对应外部队列,分散并发冲突
    int r = ThreadLocalRandom.getProbe();
    int rs = runState;
    // SQMASK 路由掩码,限制外部队列索引范围
    // rs > 0:线程池正常运行,未关闭
    // U.compareAndSwapInt(q, QLOCK, 0, 1):CAS 抢占队列锁
    if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
    	// m & r & SQMASK 一定是偶数
        (q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 &&
        U.compareAndSwapInt(q, QLOCK, 0, 1)) {
        ForkJoinTask<?>[] a; int am, n, s;
        // 判断当前任务数 < 数组容量
        if ((a = q.array) != null &&
            (am = a.length - 1) > (n = (s = q.top) - q.base)) {
            // 计算 top 下标
            int j = ((am & s) << ASHIFT) + ABASE;
            // a[j] = task
            U.putOrderedObject(a, j, task);
            // top++
            U.putOrderedInt(q, QTOP, s + 1);
            // 释放锁
            U.putIntVolatile(q, QLOCK, 0);
            // 同 WorkQueue 的 push 方法一样
            if (n <= 1)
                signalWork(ws, q);
            return;
        }
        // 不满足 if 条件则直接释放锁
        U.compareAndSwapInt(q, QLOCK, 1, 0);
    }
    externalSubmit(task);
}

externalSubmit

  • 偶数下标为外部共享队列,奇数下标为 worker 私有队列

  • 数组为空则先进行初始化操作,然后再次循环

  • 数组随机下标不为空,获取 WorkQueue ,将 task 存入到对应的 task 数组中,更新首尾指针

  • 数组随机下标元素为空,创建 WorkQueue 并放入该下标中

java 复制代码
private void externalSubmit(ForkJoinTask<?> task) {
	int r;                                    
	if ((r = ThreadLocalRandom.getProbe()) == 0) {
	    ThreadLocalRandom.localInit();
	    // 路由到不同共享队列分散并发冲突
	    r = ThreadLocalRandom.getProbe();
	}
	
	// 只有成功提交任务 return 或池关闭抛异常才退出
	for (;;) {
	    WorkQueue[] ws; WorkQueue q; int rs, m, k;
	    // move=true 代表当前路由队列竞争失败,更新探针重新哈希
	    boolean move = false;
	    // runState < 0,池处于 shutdown/terminate 状态
	    if ((rs = runState) < 0) {
	        tryTerminate(false, false);     
	        throw new RejectedExecutionException();
	    }
	    else if ((rs & STARTED) == 0 ||     
	             ((ws = workQueues) == null || (m = ws.length - 1) < 0)) {
	        int ns = 0;
	        // 通过 CAS 抢占全局锁,防止多线程并发初始化数组
	        rs = lockRunState();
	        try {
	            if ((rs & STARTED) == 0) {
	            	// 初始化偷任务计数器
	                U.compareAndSwapObject(this, STEALCOUNTER, null,
	                                       new AtomicLong());
	                 // 计算数组长度(2的幂)
	                int p = config & SMASK; 
	                int n = (p > 1) ? p - 1 : 1;
	                n |= n >>> 1; n |= n >>> 2;  n |= n >>> 4;
	                n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1;
	                workQueues = new WorkQueue[n];
	                ns = STARTED;
	            }
	        } finally {
	            unlockRunState(rs, (rs & ~RSLOCK) | ns);
	        }
	    }
	    // 对应下标已经有队列
	    // m & r & SQMASK 一定是偶数
	    else if ((q = ws[k = r & m & SQMASK]) != null) {
	    	// 抢锁
	        if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) {
	            ForkJoinTask<?>[] a = q.array;
	            int s = q.top;
	            boolean submitted = false; 
	            try {
	            	// 两种情况可写入:数组有空闲 || 扩容成功                      
	                if ((a != null && a.length > s + 1 - q.base) ||
	                    (a = q.growArray()) != null) {
	                    int j = (((a.length - 1) & s) << ASHIFT) + ABASE;
	                    U.putOrderedObject(a, j, task);
	                    U.putOrderedInt(q, QTOP, s + 1);
	                    submitted = true;
	                }
	            } finally {
	                U.compareAndSwapInt(q, QLOCK, 1, 0);
	            }
	            if (submitted) {
	                signalWork(ws, q);
	                return;
	            }
	        }
	        move = true;
	    }
	    // 路由下标 k 位置队列为 null,新建共享 WorkQueue
	    else if (((rs = runState) & RSLOCK) == 0) {
	    	// 创建新的队列
	        q = new WorkQueue(this, null);
	        q.hint = r;
	        // 标记为共享队列
	        q.config = k | SHARED_QUEUE;
	        q.scanState = INACTIVE;
	        // 抢占全局锁并更新队列引用到 workQueues[k]
	        rs = lockRunState();
	        if (rs > 0 &&  (ws = workQueues) != null &&
	            k < ws.length && ws[k] == null)
	            ws[k] = q;
	        unlockRunState(rs, rs & ~RSLOCK);
	    }
	    else
	        move = true;
	    if (move)
	        r = ThreadLocalRandom.advanceProbe(r);
	}
}

signalWork

  • 查看是否有空闲 worker,没有则进行创建
  • 获取空闲 WorkQueue,更改队列状态为活跃,更新 ctl,唤醒 WorkQueue 的对应工作线程
ctl
  • 高 32 位:活跃线程计数(AC),AC = 活跃线程数 - parallelism,AC < 0 说明当前的活跃线程 < parallelism 可以进行新建,AC < 0 -> ctl < 0
  • 低 32 位:空闲 Worker 栈顶的 scanState(sp),当 Worker 没活干时,它不会销毁,而是把自己压入一个栈,然后休眠(park),每个 WorkQueue 里有一个字段 scanState,通过 scanState 能够唯一确定一个 workQueue,scanState = 0 表明当前栈顶没有任何对象,即没有空闲 Worker
java 复制代码
final void signalWork(WorkQueue[] ws, WorkQueue q) {
    long c; int sp, i; WorkQueue v; Thread p;
    // ctl < 0:代表活跃线程不足,存在休眠 Worker / 需要新建线程
    while ((c = ctl) < 0L) {
    	// (int)c → 取低 32 位 → 栈顶空闲 Worker
    	// sp == 0 -> 没有空闲 worker
        if ((sp = (int)c) == 0) {
        	// 允许创建 worker,ADD_WORKER = 0x0001L << (TC_SHIFT + 15) -> bit 47
        	// 也就是说 bit 47 是一个标志位,该位 = 1 表明允许扩容,否则不能扩容
            if ((c & ADD_WORKER) != 0L)
                tryAddWorker(c);
            break;
        }
        // 状态检查,未开启/已终止
        if (ws == null)
            break;
        // 数组越界说明已终止
        // i = sp & SMASK 计算当前空闲 Worker 的下标
        if (ws.length <= (i = sp & SMASK))
            break;
        // 对应下标队列不存在,说明 Worker 已被回收
        if ((v = ws[i]) == null)
            break;
        // 计算唤醒后的新 scanState
        // sp + SS_SEQ 更新版本号防止 ABA 问题
        // & ~INACTIVE 清掉休眠位将 Worker 状态变为活跃
        int vs = (sp + SS_SEQ) & ~INACTIVE;
        // 确认栈顶还是不是当前的 Worker
        int d = sp - v.scanState;
        // 等价于 (ac + 1) | stackPred
        long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
        // d == 0 -> sp = v.scanState,如果不相等说明这个 Worker 已经被别人唤醒了
        if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
            v.scanState = vs;
            if ((p = v.parker) != null)
                U.unpark(p);
            break;
        }
        // CAS 唤醒失败(被其他线程抢先唤醒),判断当前触发 signalWork 的队列是否已经空了
        // base == top 队列无任务,没必要继续循环重试唤醒,直接 break
        if (q != null && q.base == q.top) 
            break;
    }
}

tryAddWorker

  • 更新 ctl (AC + 1,TC + 1)
  • 创建 Worker
java 复制代码
private void tryAddWorker(long c) {
    boolean add = false;
    do {
    	// AC + 1 并且 TC + 1
        long nc = ((AC_MASK & (c + AC_UNIT)) |
                   (TC_MASK & (c + TC_UNIT)));
        if (ctl == c) {
            int rs, stop;
            if ((stop = (rs = lockRunState()) & STOP) == 0)
                add = U.compareAndSwapLong(this, CTL, c, nc);
            unlockRunState(rs, rs & ~RSLOCK);
            if (stop != 0)
                break;
            if (add) {
                createWorker();
                break;
            }
        }
        // 允许扩容并且 scanState 栈顶为空 -> 没有空闲 worker
    } while (((c = ctl) & ADD_WORKER) != 0L && (int)c == 0);
}

createWorker

  • 创建并启动 ForkJoinWorkerThread
java 复制代码
private boolean createWorker() {
    ForkJoinWorkerThreadFactory fac = factory;
    Throwable ex = null;
    ForkJoinWorkerThread wt = null;
    try {
    	// 创建并启动 ForkJoinWorkerThread 新线程,传入的是当前 ForkJoinPool,代表线程在该 pool 中运行
        if (fac != null && (wt = fac.newThread(this)) != null) {
            wt.start();
            return true;
        }
    } catch (Throwable rex) {
        ex = rex;
    }
    deregisterWorker(wt, ex);
    return false;
}

deregisterWorker

java 复制代码
final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) {
    WorkQueue w = null;
    if (wt != null && (w = wt.workQueue) != null) {
        WorkQueue[] ws;
        // 取出当前线程对应的下标
        int idx = w.config & SMASK;
        int rs = lockRunState();
        if ((ws = workQueues) != null && ws.length > idx && ws[idx] == w)
        	// 释放 WorkQueue
            ws[idx] = null;
        unlockRunState(rs, rs & ~RSLOCK);
    }
    long c;                                       // decrement counts
    do {} while (!U.compareAndSwapLong
    			// AC - 1 并且 TC - 1,scanState 保持不变
                 (this, CTL, c = ctl, ((AC_MASK & (c - AC_UNIT)) |
                                       (TC_MASK & (c - TC_UNIT)) |
                                       (SP_MASK & c))));

	// 
    if (w != null) {
    	// 队列锁永久失效状态,阻止其他线程再往该队列提交 / 窃取任务
        w.qlock = -1;
        // 把当前队列的任务窃取统计数据合并到线程池全局统计
        w.transferStealCount(this);
        // 遍历队列里所有未执行任务,标记为取消,释放任务资源,不再执行
        w.cancelAll();                            // cancel remaining tasks
    }
    for (;;) {                                    // possibly replace
        WorkQueue[] ws; int m, sp;
        // 池正在关闭/队列失效/已停止,直接跳出不补线程
        if (tryTerminate(false, false) || w == null || w.array == null ||
            (runState & STOP) != 0 || (ws = workQueues) == null ||
            (m = ws.length - 1) < 0)              // already terminating
            break;
        // 有休眠等待的线程,唤醒一个顶替退出的线程
        if ((sp = (int)(c = ctl)) != 0) {         // wake up replacement
            if (tryRelease(c, ws[sp & m], AC_UNIT))
                break;
        }
        // 线程异常退出 && 池允许新增线程,新建工作线程替补
        else if (ex != null && (c & ADD_WORKER) != 0L) {
            tryAddWorker(c);                      // create replacement
            break;
        }
        else                                      // don't need replacement
            break;
    }
    if (ex == null)                               // help clean on way out
        ForkJoinTask.helpExpungeStaleExceptions();
    else                                          // rethrow
        ForkJoinTask.rethrow(ex);
}

registerWorker

  • 创建 WorkQueue
  • 如果 WorkQueues 中对应下标不为空,则触发数组扩容
  • 如果 WorkQueues 中对应下标为空,则将新创建的 WorkQueue 放入该下标中
java 复制代码
final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
    UncaughtExceptionHandler handler;
    // ForkJoin 线程统一设为守护线程
    wt.setDaemon(true);
    // 绑定异常处理器
    if ((handler = ueh) != null)
        wt.setUncaughtExceptionHandler(handler);
    // WorkQueue  绑定当前 pool 与工作线程
    WorkQueue w = new WorkQueue(this, wt);
    int i = 0;
    //从配置中提取运行模式(同步 / 异步模式)
    int mode = config & MODE_MASK;
    int rs = lockRunState();
    try {
        WorkQueue[] ws; int n;
        // 数组为空 / 长度为 0 时,直接跳过分配下标逻辑,i 保持 0
        if ((ws = workQueues) != null && (n = ws.length) > 0) {
        	// 随机计算下标,减少哈希冲突
            int s = indexSeed += SEED_INCREMENT;
            int m = n - 1;
            // | 1 说明结果一定是奇数 -> 内部工作线程的 WorkerQueue 一定是奇数下标
            i = ((s << 1) | 1) & m;
            // 计算出的奇数下标已经存在其他 Worker 的队列,进入线性探测扩容逻辑
            if (ws[i] != null) {
                int probes = 0;
                // step 永远是偶数,保证下标永远是奇数
                int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2;
                while (ws[i = (i + step) & m] != null) {
                	// 如果当前 workQueues 没有空位,触发数组扩容
                    if (++probes >= n) {
                        workQueues = ws = Arrays.copyOf(ws, n <<= 1);
                        m = n - 1;
                        probes = 0;
                    }
                }
            }
            w.hint = s;                           // use as random seed
            w.config = i | mode;
            w.scanState = i;                      // publication fence
            ws[i] = w;
        }
    } finally {
        unlockRunState(rs, rs & ~RSLOCK);
    }
    wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1)));
    return w;
}

runWorker

  • 初始化 WorkQueue 的任务数组
  • 循环扫描任务并执行
  • 没有任务了则会等待任务
java 复制代码
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);
        // 等待任务,awaitWork 返回 false:池子关闭 / 当前线程需要被回收
        else if (!awaitWork(w, r))
            break;
        // 更新随机种子 r
        r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
    }
}

scan

  • 遍历 WorkQueue 数组
  • 如果数组不为空,获取队尾的任务进行返回
  • 遍历数组都为空,说明当前没有任务,将当前 WorkQueue 状态置为 INACTIVE,修改 ctl 将 AC - 1,
java 复制代码
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;                     // initially non-negative
        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) {
            	// q.base - q.top < 0 说明队列中存在多余任务
                if ((n = (b = q.base) - q.top) < 0 &&
                    (a = q.array) != null) {      // non-empty
                    // 计算队列 base 对应任务在数组内存的偏移地址
                    long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
                    // 读取数组 base 位置的任务,并且队列头部 base 没有被其他线程修改
                    // 防止读取途中其他线程抢先偷走任务,导致读到脏数据
                    if ((t = ((ForkJoinTask<?>)
                              U.getObjectVolatile(a, i))) != null &&
                        q.base == b) {
                        // ss >= 0 说明 workQueue 归属的线程状态是活跃
                        if (ss >= 0) {
                        	// 窃取任务,a[i] = null
                            if (U.compareAndSwapObject(a, i, t, null)) {
                                q.base = b + 1;
                                // n < -1 说明还有剩余任务
                                if (n < -1)
                                    signalWork(ws, q);
                                return t;
                            }
                        }
                        else if (oldSum == 0 && w.scanState < 0)
                            // 把自己从空闲状态激活,唤醒自己继续干活
                            tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
                    }
                    // 同步最新 scanState
                    if (ss < 0)
                        ss = w.scanState;
                    // 换一个全新的随机起点
                    r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
                    origin = k = r & m;           // move and rescan
                    oldSum = checkSum = 0;
                    continue;
                }
                // 队列是空的,把 base 值累加进 checkSum
                checkSum += b;
            }
            // k = k+1 & m:下标向后移动一位,环形遍历
            // == origin 说明已经绕了数组一圈回到了起点
            if ((k = (k + 1) & m) == origin) {
            	// 线程保持活跃,或刷新后 scanState 没变
            	// oldSum == 当前轮 checkSum 说明完整扫完一圈,全局所有队列任务总量没有变化,没有新任务产生
                if ((ss >= 0 || (ss == (ss = w.scanState))) &&
                    oldSum == (oldSum = checkSum)) {
                    // 线程已经标记空闲 / 队列加锁,直接跳出循环
                    if (ss < 0 || w.qlock < 0)
                        break;
                    // 把当前 scanState 打上 INACTIVE 空闲标记
                    int ns = ss | INACTIVE;
                    // AC - 1
                    long nc = ((SP_MASK & ns) |
                               (UC_MASK & ((c = ctl) - AC_UNIT)));
                    w.stackPred = (int)c;
                    // 修改 scanState
                    U.putInt(w, QSCANSTATE, ns);
                    if (U.compareAndSwapLong(this, CTL, c, nc))
                        ss = ns;
                    else
                    	// 修改 ctl 失败,回滚状态,取消空闲标记
                        w.scanState = ss;         // back out
                }
                checkSum = 0;
            }
        }
    }
    return null;
}

tryRelease

  • 弹出全局空闲栈最顶部的空闲线程,修改 ctl、恢复活跃计数、唤醒阻塞线程
  • 唤醒成功返回 true,失败返回 false
java 复制代码
private boolean tryRelease(long c, WorkQueue v, long inc) {
	// 获取当前的 scanState,并将状态改为活跃状态
    int sp = (int)c, vs = (sp + SS_SEQ) & ~INACTIVE; Thread p;
    // 队列非空并且 scanState 没有变化,确认 v 确实是当前全局空闲栈最顶部的线程
    if (v != null && v.scanState == sp) {
    	// UC_MASK & (c + inc):高 32 位活跃计数器 AC + 1
    	// SP_MASK & v.stackPred:低 32 位替换成 v 保存的上一层栈顶 stackPred
        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;
}

awaitWork

  • 自旋指定次数,查看当前 WorkQueue 状态是否变为 ACTIVE
  • 自旋指定次数后,仍然没有变为 ACTIVE,则 park 工作线程,等待唤醒
java 复制代码
private boolean awaitWork(WorkQueue w, int r) {
	// 判断队列是否存在以及队列是否正在销毁中
    if (w == null || w.qlock < 0)
        return false;
    for (int pred = w.stackPred, spins = SPINS, ss;;) {
    	// scanState >= 0 说明当前线程已经被唤醒,直接退出循环
        if ((ss = w.scanState) >= 0)
            break;
        // 判断剩余自旋次数
        else if (spins > 0) {
        	// 更新随机种子
            r ^= r << 6; r ^= r >>> 21; r ^= r << 7;
            // spin 次数耗尽
            if (r >= 0 && --spins == 0) {
                WorkQueue v; WorkQueue[] ws; int s, j; AtomicLong sc;
                // 上一个栈节点 v 已经活跃,说明全局任务可能变多
                if (pred != 0 && (ws = workQueues) != null &&
                    (j = pred & SMASK) < ws.length &&
                    (v = ws[j]) != null &&
                    // scanState >= 0 说明线程已经被唤醒
                    (v.parker == null || v.scanState >= 0))
                    // 重置自旋次数,继续自旋,不立刻 park,减少线程切换开销
                    spins = SPINS;
            }
        }
        else if (w.qlock < 0)
            return false;
        // 自旋耗尽、队列正常、线程未被中断,进入阻塞 park 逻辑
        else if (!Thread.interrupted()) {
            long c, prevctl, parkTime, deadline;
            // ac ≤ 0 代表当前没有活跃干活线程
            int ac = (int)((c = ctl) >> AC_SHIFT) + (config & SMASK);
            // 无活跃线程,池子触发终止流程 || 池子状态为 STOP 关闭中
            if ((ac <= 0 && tryTerminate(false, false)) ||
                (runState & STOP) != 0)
                return false;
            // 判断当前是全局最后一个空闲等待线程
            if (ac <= 0 && ss == (int)c) {
            	// 活跃线程 + 1,栈顶指向上一层(弹出自己)
                prevctl = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & pred);
                // 记录当前冗余空闲线程数量
                int t = (short)(c >>> TC_SHIFT);
                // 冗余空闲线程超过 2 个,直接 CAS 弹出当前线程
                if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl))
                    return false;
                // 设置定时阻塞时长
                parkTime = IDLE_TIMEOUT * ((t >= 0) ? 1 : 1 - t);
                deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
            }
            else
            	// 还有其他活跃线程,无限阻塞 park
                prevctl = parkTime = deadline = 0L;
            Thread wt = Thread.currentThread();
            // 把当前线程存入队列 parker,标记阻塞持有者,供tryRelease唤醒使用
            U.putObject(wt, PARKBLOCKER, this);
            w.parker = wt;
            // 双重校验防止并发唤醒竞争
            if (w.scanState < 0 && ctl == c)
                U.park(false, parkTime);
            U.putOrderedObject(w, QPARKER, null);
            U.putObject(wt, PARKBLOCKER, null);
            // 唤醒后发现自己已经被标记活跃 → 跳出循环
            if (w.scanState >= 0)
                break;
            if (parkTime != 0L && ctl == c &&
                deadline - System.nanoTime() <= 0L &&
                U.compareAndSwapLong(this, CTL, c, prevctl))
                // 时间到、ctl 没变化、CAS 弹出自己出空闲栈,返回 false 销毁线程,缩小池子空闲线程数量
                return false;
        }
    }
    return true;
}

awaitJoin

  • 更新 currentJoin 属性,让其他线程偷取任务时能够获取并执行当前 join 的任务
  • 本地队列没有任务,则去偷窃其他队列的任务执行
  • 当前任务在自身队列中,则直接执行
  • 判断当前线程数是否充足
    • 如果充足,当前线程就会 wait
    • 如果不充足,当前线程会重复上述流程
java 复制代码
final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
    int s = 0;
    if (task != null && w != null) {
        // 保存当前队列正在等待的任务,之后恢复
        ForkJoinTask<?> prevJoin = w.currentJoin;
        // 别的线程执行任务偷窃helpStealer()的时候,会读取targetWorkQueue.currentJoin
        U.putOrderedObject(w, QCURRENTJOIN, task);
        CountedCompleter<?> cc = (task instanceof CountedCompleter) ?
            (CountedCompleter<?>)task : null;
        for (;;) {
            // 先检测任务状态,如果任务已经结束(status < 0)直接跳出循环
            if ((s = task.status) < 0)
                break;
            if (cc != null)
                helpComplete(w, cc, 0);
            // w.base == w.top:自己队列已经空了,没有本地任务,只能去偷别人任务
            // w.tryRemoveAndExec(task):尝试把当前正在等待的 task 拿出来自己执行
            // 等待的任务刚好就在自己队列里,直接自己执行它,不用等别人
            else if (w.base == w.top || w.tryRemoveAndExec(task))
                // 当前线程去全局扫描其他队列,偷窃任务执行。
                // 一边偷任务跑,一边观察:我等待的目标任务有没有完成。
                // 只要目标任务完成,立刻退出循环,结束 join。
                helpStealer(w, task);
                
            // helpComplete / helpStealer 一轮干活结束,再次检查任务是否完成
            if ((s = task.status) < 0)
                break;
            long ms, ns;
            if (deadline == 0L)
                ms = 0L;
            else if ((ns = deadline - System.nanoTime()) <= 0L)
                // 超时,退出循环
                break;
            else if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) <= 0L)
                // 不足1毫秒,最低等待1ms
                ms = 1L;
            
            // 如果池子里任务很多、线程不够 → 返回 false,禁止阻塞!继续循环偷任务,不能休眠
            // 如果线程充足,存在空闲条件 → 返回 true,允许短暂阻塞
            if (tryCompensate(w)) {
                // 进入任务内部wait阻塞
                // ms > 0 不会一直死等,定时醒来继续工作窃取
                task.internalWait(ms);
                // CTL + AC_UNIT:标记线程正在阻塞
                U.getAndAddLong(this, CTL, AC_UNIT);
            }
        }
        // 恢复队列原本等待的任务,栈复原
        U.putOrderedObject(w, QCURRENTJOIN, prevJoin);
    }
    return s;
}

ForkJoinWorkerThread

构造方法

java 复制代码
protected ForkJoinWorkerThread(ForkJoinPool pool) {
    // Use a placeholder until a useful name can be set in registerWorker
    super("aForkJoinWorkerThread");
    this.pool = pool;
    this.workQueue = pool.registerWorker(this);
}

run

createWorker 方法中调用了 start,实际会运行 run 方法

java 复制代码
public void run() {
	// 确保只运行一次
    if (workQueue.array == null) {
        Throwable exception = null;
        try {
            onStart();
            // 池子关闭 / 当前线程需要被回收后会退出 runWorker
            pool.runWorker(workQueue);
        } catch (Throwable ex) {
            exception = ex;
        } finally {
            try {
                onTermination(exception);
            } catch (Throwable ex) {
                if (exception == null)
                    exception = ex;
            } finally {
                pool.deregisterWorker(this, exception);
            }
        }
    }
}

总结

调用流程

plaintext 复制代码
一、外部线程提交任务
invoke/submit/execute
    └→ externalSubmit
        └→ WorkQueue.push(共享队列)
            └→ signalWork
                ├→ tryAddWorker 创建Worker
                └→ unpark 唤醒空闲Worker
                    └→ Worker.run → runWorker 循环
                        └→ scan
                            ├→ pollShared 读取共享任务
                            └→ doExec 执行任务
                                ├→ compute(业务逻辑)
                                └→ join 阻塞等待结果

二、Worker内部 fork 子任务
compute() → fork()
    └→ externalPush
        ├→ Worker线程:私有WorkQueue.push(头部)
        └→ 外部线程:共享队列push
            └→ signalWork 通知窃取
                └→ 其他Worker scan → poll(其他队列尾部) → doExec

三、join阻塞流程
join() → doJoin()
    ├ 当前线程 = Worker:helpJoin() → 循环scan窃取任务(防死锁)
    └ 普通线程:awaitDone() → park阻塞
任务完成后 doExec → signalComplete → unpark唤醒join等待线程

四、Worker主调度循环
runWorker()
    while(未关闭)
        scan() 三级找任务(自身→偷别人→共享队列)
            有任务 → doExec()
            无任务 → awaitWork() park休眠

任务窃取机制

队列模型

  • 每个 ForkJoinWorkerThread 拥有专属无锁双端队列 WorkQueue
  • 队列分两种:工作线程队列(有 owner)、外部提交队列(无 owner,共享)
  • 存取规则:
    1. 本线程执行:pop () 队尾出队,减少递归拆分深度
    2. 窃取线程:poll () 队头偷任务,最小化锁竞争
    3. 外部提交任务统一放到共享队列,由空闲线程消费

窃取流程

  1. 随机遍历其他工作线程队列,尝试从队头窃取;
  2. 全部队列无任务则阻塞休眠,有新任务唤醒;
  3. 避免竞争:偷队头、本地取队尾,读写分离减少 CAS 冲突。

工作窃取解决的优点

  • 避免线程饥饿
  • 多核 CPU 充分并行,均衡任务负载
  • 相比普通线程池,递归任务锁开销极低

ForkJoinPool 核心场景与优势

  • 核心场景:适用于大规模可拆分计算场景
  • 优势:工作窃取
    • 自己线程从队尾取任务
    • 空闲线程从其他线程队头偷任务

构造参数

java 复制代码
public ForkJoinPool(int parallelism,
                    ForkJoinWorkerThreadFactory factory,
                    UncaughtExceptionHandler handler,
                    boolean asyncMode)
  • parallelism:并行度,默认 Runtime.getRuntime().availableProcessors()
    代表期望同时运行的工作线程数,不是最大线程数
  • factory:自定义工作线程工厂,默认创建 ForkJoinWorkerThread
  • handler:任务未捕获异常处理器
  • asyncMode:异步模式(队列存取策略)
    • false (默认):LIFO(自身工作线程从队尾取任务,适合递归分治计算)
    • true:FIFO(自身工作线程从本地队头取,适合异步事件任务)

fork 底层逻辑

  1. 判断当前线程是否为 ForkJoinWorkerThread
    • 是:把任务 push 到当前线程本地 WorkQueue
    • 否:外部线程,任务丢到共享队列,唤醒工作线程
  2. 触发线程调度,空闲线程可窃取

join 核心逻辑

  • 循环读取任务 status,判断是否完成;
    • 未完成:
      • 如果当前是工作线程:帮助窃取执行其他任务 → 不会自旋阻塞,而是主动偷任务干活,充分利用 CPU
      • 如果是普通外部线程:park 阻塞等待唤醒
    • 任务完成:
      • NORMAL:直接返回结果
      • EXCEPTIONAL:包装抛出 CompletionException
      • CANCELLED:抛出 CancellationException

fork 和 submit 的区别

  • fork:仅提交任务,不阻塞,只能在 ForkJoinWorkerThread 内调用,放入本地队列
  • submit:通用提交方法,外部线程可用,返回 ForkJoinTask,底层会区分内外线程放入对应队列

RecursiveTask 和 RecursiveAction 区别

  • RecursiveTask 有返回值,重写 compute 返回泛型结果
  • RecursiveAction 无返回值,compute 返回 void

workQueues

  • 每个 WorkQueue 对应一个工作线程的双端队列
  • 奇数下标:属于某个工作线程,内部 deque 存放该线程 fork 出来的子任务
  • 偶数下标:外部提交任务的公共队列
  • 工作窃取:空闲线程扫描其他 WorkQueue,从队列尾部 pop 任务

ForkJoinPool 线程什么时候创建 / 销毁

  • 任务提交后,当现有活跃线程不够,会创建新的ForkJoinWorkerThread;
  • 线程没有任务时,不会立刻销毁,会休眠等待;
  • 空闲线程会超时回收;受 parallelism 控制,会维持期望并行度;
  • commonPool 线程不会主动全部销毁,JVM 退出才结束。
相关推荐
wear工程师1 小时前
Kafka 消费者心跳正常,为什么还会被踢出组?拆清 max.poll.interval.ms
java·kafka
SemiTris2 小时前
为什么 C/C++ 不走 Java 式的虚拟机跨平台路线?
java·程序员
weixin_440784112 小时前
【IntentSeivice实现原理】
android·java·开发语言·intentservice
IanSkunk2 小时前
企业AI Agent生产化落地:从技术架构到实施服务的全链路分析
java·人工智能·架构
岁岁养乐多2 小时前
Java 序列化相关问题
java·开发语言
小短腿乄3 小时前
java实现pdf加水印+签名
java·开发语言·pdf
云烟成雨TD3 小时前
Micrometer 系列【29】源码分析:MeterRegistry 实例化流程
java·云原生·micrometer
聆风吟º3 小时前
【金仓数据库征文】Java 应用接入金仓数据库:从驱动、连接池到存储过程调试的踩坑实录
java·开发语言·数据库
西凉的悲伤4 小时前
Spring Boot 中 Filter 过滤器详解
java·spring boot·后端·过滤器·filter