ForkJoinTask
任务分类
- ForkJoinTask 抽象任务基类
- RecursiveTask 继承 ForkJoinTask 有返回值递归任务
- RecursiveAction 继承 ForkJoinTask 无返回值递归任务
fork
- 第一次 fork:调用线程执行 compute → currentThread() = main,非 worker,调用池 externalPush
- 递归内层 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,保证 length - 1 合法
- 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
- 通过随机 probe 哈希映射到一个共享 WorkQueue
- 外部任务存入队列头部,供 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,共享)
- 存取规则:
- 本线程执行:pop () 队尾出队,减少递归拆分深度
- 窃取线程:poll () 队头偷任务,最小化锁竞争
- 外部提交任务统一放到共享队列,由空闲线程消费
窃取流程
- 随机遍历其他工作线程队列,尝试从队头窃取;
- 全部队列无任务则阻塞休眠,有新任务唤醒;
- 避免竞争:偷队头、本地取队尾,读写分离减少 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 底层逻辑
- 判断当前线程是否为 ForkJoinWorkerThread
- 是:把任务 push 到当前线程本地 WorkQueue
- 否:外部线程,任务丢到共享队列,唤醒工作线程
- 触发线程调度,空闲线程可窃取
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 退出才结束。