Fork/Join框架是Java并发工具包中的一种可以将一个大任务拆分为很多小任务来异步执行的工具,自JDK1.7引入。
Fork/Join框架主要包含三个模块:
1、任务对象:ForkJoinTask
2、执行Fork/Join任务的线程:ForkJoinWorkerThread
3、线程池:ForkJoinPool
这三者的关系是:ForkJoinPool可以通过池中的ForkJoinWorkerThread来处理ForkJoinTask任务。
其基本的使用模板如下(转自《A Java Fork/Join Framework》Dong Lea著):
Result solve(Problem problem) {
if (problem is small)
directly solve problem
else {
split problem into independent parts
fork new subtasks to solve each part
join all subtasks
compose result from subresults
}
}
大致意思是:如果这个problem已经足够小,那么通过当前线程执行这个小任务。否则,将任务分割,然后fork执行这些分割后的任务,再join合并这些任务的结果。
Fork/Join框架的源码实现较为复杂,建议读者在阅读文章前至少先要明白其使用方法。
ForkJoinTask继承关系图:
ForkJoinTask在Java并发工具包中还有两个子类:RecursiveTask和RecursiveAction。前者代表有返回值的任务,后者代表没有返回值的任务。
博主水平有限,如果本文有任何错误或不足之处欢迎指出。
ForkJoinTask的API如下:
方法签名 | 作用 |
---|---|
final ForkJoinTask |
向线程池提交任务,返回this |
final V join() | 获取任务执行结果,如果仍然尚未执行那么会立刻执行任务 |
final V invoke() | 通过当前线程直接执行任务 |
boolean cancel(boolean) | 取消任务,boolean参数可以随意指定,没有作用 |
final boolean isDone() | 返回任务是否完成 |
final boolean isCancelled() | 返回任务是否被取消 |
final boolean isCompletedAbnormally() | 判断任务是不是非正常执行完成的(被取消或是抛出未捕获异常) |
final boolean isCompletedNormally() | 判断任务是否正常执行完成 |
final Throwable getException() | 获得任务执行期间抛出的未捕获异常(如果有的话) |
void completeExceptionally(Throwable) | 当任务执行期间抛出指定的异常时忽略 |
void complete(V) | 设置任务完成后的值,并强制将任务设为正常完成 |
final void quietlyComplete() | 强制将任务设置为已正常完成 |
final V get() | 获得任务执行的结果,若尚未执行结束则阻塞 |
final V get(long,TimeUnit) | 获得任务执行的结果,若尚未执行结束则阻塞至多指定的时间 |
final void quietlyJoin() | 快速join任务而不获得执行结果 |
final void quietlyInvoke() | 直接执行任务 |
void reinitialize() | 重置任务,恢复到初始状态 |
boolean tryUnfork() | 尝试从线程池中取出该任务 |
final short getForkJoinTaskTag() | 返回此任务的标签值 |
final short setForkJoinTaskTag(short) | 设置此任务的标签值 |
final boolean compareAndSetForkJoinTaskTag(short, short) | 通过CAS将任务标签值由第一个参数设为第二个参数 |
示例:采用Fork/Join来异步计算1+2+3+…+10000的结果
public class Test {
static final class SumTask extends RecursiveTask<Integer> {
private static final long serialVersionUID = 1L;
final int start; //开始计算的数
final int end; //最后计算的数
SumTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
//如果计算量小于1000,那么分配一个线程执行if中的代码块,并返回执行结果
if(end - start < 1000) {
System.out.println(Thread.currentThread().getName() + " 开始执行:" + start + "-" + end);
int sum = 0;
for(int i = start; i <= end; i++)
sum += i;
return sum;
}
//如果计算量大于1000,那么拆分为两个任务
SumTask task1 = new SumTask(start, (start + end) / 2);
SumTask task2 = new SumTask((start + end) / 2 + 1, end);
//执行任务
task1.fork();
task2.fork();
//获取任务执行的结果
return task1.join() + task2.join();
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Integer> task = new SumTask(1, 10000);
pool.submit(task);
System.out.println(task.get());
}
}
执行结果如下:
ForkJoinPool-1-worker-1 开始执行:1-625
ForkJoinPool-1-worker-7 开始执行:6251-6875
ForkJoinPool-1-worker-6 开始执行:5626-6250
ForkJoinPool-1-worker-10 开始执行:3751-4375
ForkJoinPool-1-worker-13 开始执行:2501-3125
ForkJoinPool-1-worker-8 开始执行:626-1250
ForkJoinPool-1-worker-11 开始执行:5001-5625
ForkJoinPool-1-worker-3 开始执行:7501-8125
ForkJoinPool-1-worker-14 开始执行:1251-1875
ForkJoinPool-1-worker-4 开始执行:9376-10000
ForkJoinPool-1-worker-8 开始执行:8126-8750
ForkJoinPool-1-worker-0 开始执行:1876-2500
ForkJoinPool-1-worker-12 开始执行:4376-5000
ForkJoinPool-1-worker-5 开始执行:8751-9375
ForkJoinPool-1-worker-7 开始执行:6876-7500
ForkJoinPool-1-worker-1 开始执行:3126-3750
50005000
ForkJoinTask是一个抽象类,子类需要重写它的setRawResult、getRawResult及其exec方法。
setRawResult方法用于对执行完成的结果进行后处理,getRawResult返回原始的执行结果,exec方法包含任务的执行逻辑及其任务分割操作。
对于RecursiveTask和RecursiveAction这两个子类,已经重写了其中的setRawResult和getRawResult方法,只需要重写exec方法即可。
ForkJoinPool可以用来执行ForkJoinTask任务,其内部维护了一组线程和任务队列,可以用来高效地执行任务。
ForkJoinPool包含以下成员变量:
类型及其修饰符 | 变量名 | 作用 |
---|---|---|
volatile long | ctl | 保存线程池线程数量,分为4个区域保存(每16位为1个区域) |
volatile int | runState | 保存线程池的运行状态 |
final int | config | 保存线程池的最大线程数量及其是否采用了公平模式 |
int | indexSeed | 用于在构造WorkQueue时计算插入到workQueues的下标 |
volatile WorkQueue[] | workQueues | 线程池持有的工作线程(即执行任务的线程) |
final ForkJoinWorkerThreadFactory | factory | 该线程池指定的线程工厂,用于生产ForkJoinWorkerThread对象 |
final String | workerNamePrefix | 该线程池中工作线程的名称前缀 |
volatile AtomicLong | stealCounter | 该线程池中所有的WorkQueue总共被窃取的任务数量 |
(1)ctl变量
JDK源码中对ctl变量是这么解释的:
Field “ctl” contains 64 bits holding information needed to atomically decide to add, inactivate, enqueue (on an event queue), dequeue, and/or re-activate workers. To enable this packing, we restrict maximum parallelism to (1<<15)-1 (which is far in excess of normal operating range) to allow ids, counts, and their negations (used for thresholding) to fit into 16bit subfields.
根据其它源码可以总结出ctl变量作用为:
ctl负责保存当前线程池的线程数量(通过位来保存,并非ctl等于线程数量):49-64位保存活跃的线程数量, 33-48位保存所有的线程数量,17~32为保存Idle Worker栈的栈顶Idle Worker是ACTIVE还是INACTIVE的(Worker的ACTIVE和INACTIVE我们稍作讨论),1 ~ 16位保存这个Idle Worker在workQueues的下标
其相关的常量如下:
//表示高32位
private static final long SP_MASK = 0xffffffffL;
//表示低32位
private static final long UC_MASK = ~SP_MASK;
//AC表示active count,即活跃的线程
private static final int AC_SHIFT = 48;
private static final long AC_UNIT = 0x0001L << AC_SHIFT; //代表1个活跃线程
private static final long AC_MASK = 0xffffL << AC_SHIFT; //代表33~48位
//TC表示total count,即所有的线程
private static final int TC_SHIFT = 32;
private static final long TC_UNIT = 0x0001L << TC_SHIFT; //代表1个线程
private static final long TC_MASK = 0xffffL << TC_SHIFT; //代表49~64位
private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15);
对于Idle Worker的定义,ForkJoinPool的注释是这么给出的:
An idle worker is one that cannot obtain a task to execute because none are available to steal from other threads, and there are no pending submissions to the pool.
大致意思是:Idle Worker是一个空闲的Worker,它没有获取到执行的任务,因为没有可从其它Worker窃取到的任务,也没有任务被提交到线程池。
(2)runState变量
runState变量保存了当前线程池的运行状态及其是否上锁,其状态位有:
private static final int RSLOCK = 1;
private static final int RSIGNAL = 1 << 1;
private static final int STARTED = 1 << 2;
private static final int STOP = 1 << 29;
private static final int TERMINATED = 1 << 30;
private static final int SHUTDOWN = 1 << 31;
即第1位表示上锁,第2位表示SIGNAL,第3位表示线程池已经启动,第30位表示线程池正在强制关闭(调用shutdownNow方法但尚未执行结束),第31位表示线程池已经完全关闭,第32位表示线程池正在以温和的策略关闭(调用shutdown方法但尚未执行结束)。
(3)config变量
config负责保存最大的线程数量以及公平模式:前16位保存最大线程数量,第17位为1时,代表公平模式,为0时代表非公平模式:
static final int MODE_MASK = 0xffff << 16;
static final int LIFO_QUEUE = 0;
static final int FIFO_QUEUE = 1 << 16;
static final int SHARED_QUEUE = 1 << 31;
当第32位为1时,表示共享队列模式。
ForkJoinPool提供了3个public构造方法。其方法签名和作用如下:
public ForkJoinPool():
public ForkJoinPool() {
this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
defaultForkJoinWorkerThreadFactory, null, false);
}
创建一个最大线程数和CPU核心数相同的线程池,执行顺序为非公平模式,并且指定一个默认的线程工厂,不指定线程的未捕获异常处理器。
public ForkJoinPool(int):
public ForkJoinPool(int parallelism) {
this(parallelism, defaultForkJoinWorkerThreadFactory, null, false);
}
同上,只不过可以指定这个线程池最大的线程数量。
public ForkJoinPool(int, ForkJoinWorkerThreadFactory,UncaughtExceptionHandler,boolean):
public ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler, boolean asyncMode) {
this(checkParallelism(parallelism), checkFactory(factory), handler,
asyncMode ? FIFO_QUEUE : LIFO_QUEUE, "ForkJoinPool-" + nextPoolId() + "-worker-");
checkPermission();
}
这个构造方法不仅可以指定最大线程数量,还可以自定义线程工厂,工作线程未捕获异常处理器和公平模式。
这些构造方法最终调用的是这个private构造器:
private ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler, int mode, String workerNamePrefix) {
//设置名称前缀
this.workerNamePrefix = workerNamePrefix;
this.factory = factory;
this.ueh = handler;
//设置参数,前8位保存线程数量,第9位为公平与非公平模式
this.config = (parallelism & SMASK) | mode;
long np = (long)(-parallelism);
//49~64位保存最大线程数量,33~48位同样也是
this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
}
ForkJoinPool的线程工厂和ThreadPoolExecutor不一样,ForkJoinPool要求线程工厂必须实现ForkJoinPool.ForkJoinWorkerThreadFactory接口,而对于ThreadPoolExecutor,其线程工厂要求实现java.util.concurrent.ThreadFactory接口。
ForkJoinWorkerThreadFactory源码如下:
public static interface ForkJoinWorkerThreadFactory {
public ForkJoinWorkerThread newThread(ForkJoinPool pool);
}
ForkJoinWorkerThreadFactory生产的线程类型为java.util.concurrent.ForkJoinWorkerThread,这个类是Thread的子类,这个线程类额外添加了两个成员变量:
//所属的线程池对象
final ForkJoinPool pool;
//所属的WorkQueue
final ForkJoinPool.WorkQueue workQueue;
并且它声明了一个protected构造方法:
protected ForkJoinWorkerThread(ForkJoinPool pool) {
super("aForkJoinWorkerThread");
this.pool = pool;
//在线程池中注册这个工作线程
this.workQueue = pool.registerWorker(this);
}
所以如果需要实现自己的线程工厂类,还必须要实现一个ForkJoinWorkerThread的子类。
ForkJoinWorkerThread还提供了非final protected访问权限的onStart和onTermination方法用于子类重写(默认实现为空),前者在线程启动前调用,后者在这个线程即将死亡时调用。
ForkJoinTask和ThreadPoolExecutor管理工作线程和任务队列的方式不同,ThreadPoolExecutor采用了Thread + Queue的方式,而ForkJoinTask则采用了Thread包含Queue的方式,后者可以更加高效地获取、调度任务并执行。
WorkQueue在ForkJoinPool中分为两种类型:一种是ACTIVE的,这种WorkQueue持有一个ForkJoinWorkerThread。另外一种是INACTIVE的,它没有ForkJoinWorkerThread。ForkJoinPool规定:ACTIVE类型的WorkQueue只允许出现在workQueues数组的奇数位置上,同样,偶数位只允许保存INACTIVE类型的WorkQueue。
我们先从它的成员变量开始分析:
类型及其修饰符 | 变量名 | 作用 |
---|---|---|
volatile int | scanState | 保存这个WorkQueue的类型,线程是否繁忙(仅限ACTIVE类型) |
int | stackPred | 记录前驱idle Worker的下标 |
int | nsteals | 该WorkQueue被窃取的任务的总数 |
int | hint | 用于窃取线程计算下次窃取的workQueues数组的下标 |
int | config | 前2个字节保存该WorkQueue在workQueues数组的下标,17位保存属于LIFO还是FIFO模式 |
volatile int | qlock | 一个简单的锁,0表示为加锁,1表示已加锁,小于0表示当前WorkQueue已停止 |
ForkJoinTask>[] | array | 任务队列,保存ForkJoinTask任务对象 |
volatile int | base | bash与workQueues数组长度取模的值窃取线程下次从workQueues数组取出任务的下标 |
int | top | top与workQueues数组长度取模的值即为下次将任务对象插入到workQueues数组的下标 |
final ForkJoinPool | pool | 该WorkQueue对应的线程池 |
final ForkJoinWorkerThread | owner | 该WorkQueue对应的工作线程对象(ACTIVE类型的WorkQueue不会为null) |
volatile Thread | parker | 当currentThread被park(等待)时,用来保存这个线程对象来后续unpark |
ForkJoinTask> | currentJoin | 调用join方法时等待结果的任务对象 |
ForkJoinTask> | currentSteal | 保存正在执行的从别的WorkQueue窃取过来的任务 |
(1)scanState变量
首先附上JDK的注释:
Field scanState is used by both workers and the pool to manage and track whether a worker is INACTIVE (possibly blocked waiting for a signal), or SCANNING for tasks (when neither hold it is busy running tasks). When a worker is inactivated, its scanState field is set, and is prevented from executing tasks, even though it must scan once for them to avoid queuing races. Note that scanState updates lag queue CAS releases so usage requires care. When queued, the lower 16 bits of scanState must hold its pool index. So we place the index there upon initialization (see registerWorker) and otherwise keep it there or restore it when necessary.
scanState的相关静态常量如下:
static final int SCANNING = 1;
static final int INACTIVE = 1 << 31;
static final int SS_SEQ = 1 << 16;
结合源码和注释,可以看出scanState对数据的存储分为高16位和低16位两部分:
高16位中,最高位表示WorkQueue属于ACTIVE还是INACTIVE类型,当最高位为0时表示ACTIVE类型,为1时表示INACTIVE类型。另外,还保存了这个WorkQueue对应的线程是否正在执行任务(仅ACTIVE类型)
低16位中,保存了这个WorkQueue在数组workQueues中的下标。
(2)stackPred变量
stackPred用于维护一个存储Idle Worker的栈(也可看成是一个单向链表),该变量的值代表前面一个Idle Worker在workQueues数组的下标。其栈顶的Idle Worker下标存储在ForkJoinPool的变量ctl中第1~16位,第17位保存该Idle Worker是ACTIVE类型还是INACTIVE类型。
(3)top和base变量
WorkQueue的任务队列是一个数组实现的双端队列,其top%workQueues.length的值代表新任务插入到workQueues数组的位置,bash%workQueues.length的值代表窃取线程从workQueues数组取出任务的下标。
图片转自论文《A Java Fork/Join Framework》Dong Lea著
在构造WorkQueue时,base和top的值相等并且值为4096(默认array数组的长度为8192),当array数组容量不够时,会调用growArray进行扩容,扩容后的数组长度为原先的2倍大小。
ForkJoinPool提供了submit方法提交ForkJoinTask任务:
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
if (task == null)
throw new NullPointerException();
externalPush(task);
return task;
}
submit还有其它的重载方法可以提交Runnable、Callable任务,这些submit方法会将非ForkJoinTask封装为ForkJoinTask并通过externalPush方法提交。
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 && //workQueues不为空,并且运行状态正常
(q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 && //随机选取一个WorkQueue
U.compareAndSwapInt(q, QLOCK, 0, 1)) { //尝试将任务队列的qlock变量从0设为1,上述条件均满足后继续执行
ForkJoinTask<?>[] a; int am, n, s;
//获取这个WorkQueue的任务队列,如果任务队列不为空
if ((a = q.array) != null && (am = a.length - 1) > (n = (s = q.top) - q.base)) {
//计算出目标插入位置的地址偏移量
int j = ((am & s) << ASHIFT) + ABASE;
//将任务放入这个WorkQueue的任务队列的top%(s+1)位上
U.putOrderedObject(a, j, task);
//将WorkQueue对象的top变量加1
U.putOrderedInt(q, QTOP, s + 1);
//将qlock设为0,代表解锁
U.putIntVolatile(q, QLOCK, 0);
//如果top小于base,那么激活一个线程执行任务
if (n <= 1)
signalWork(ws, q);
return;
}
//将qlock设为0
U.compareAndSwapInt(q, QLOCK, 1, 0);
}
externalSubmit(task);
}
externalPush方法首先会尝试将任务对象放入任意一个WorkQueue对象的任务队列中,成功添加后方法结束,如果此时线程池没有工作线程,那么执行externalSubmit提交任务:
private void externalSubmit(ForkJoinTask<?> task) {
int r;
//获取一个随机数
if ((r = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit();
r = ThreadLocalRandom.getProbe();
}
//线程进入自旋
for (;;) {
//ws为workQueues,q为本次循环选取的WorkQueue,rs为runState,m为workQueues数组长度减1,k为q的下标
WorkQueue[] ws; WorkQueue q; int rs, m, k;
boolean move = false;
//如果线程池已经停止,那么调用tryTerminate方法尝试终止线程池并抛出RejectedExecutionException
if ((rs = runState) < 0) {
tryTerminate(false, false);
throw new RejectedExecutionException();
} else if ((rs & STARTED) == 0 || //如果线程池不在STARTED状态
((ws = workQueues) == null || (m = ws.length - 1) < 0)) { //或者workQueues为空
int ns = 0;
//获取线程池状态
rs = lockRunState();
try {
//如果STARTED位依然为0(即线程池没有在运行)
if ((rs & STARTED) == 0) {
U.compareAndSwapObject(this, STEALCOUNTER, null, new AtomicLong());
//确保workQueues长度是2的幂次且不超过65535
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;
//构造一个新的WorkQueue数组作为workQueues
workQueues = new WorkQueue[n];
//将状态设为STARTED
ns = STARTED;
}
} finally {
//更新runState变量,并且将RSLOCK位设为0
unlockRunState(rs, (rs & ~RSLOCK) | ns);
}
//随机从workQueues选取一个WorkQueue
} else if ((q = ws[k = r & m & SQMASK]) != null) {
//如果当前WorkQueue没有加锁,那么尝试将qlock从0设为1表示加锁,成功后继续执行
if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) {
ForkJoinTask<?>[] a = q.array; //获取这个WorkQueue对象管理的任务队列
int s = q.top;
boolean submitted = false; //是否成功提交任务
try {
if ((a != null && a.length > s + 1 - q.base) || //如果还有空位可以存放任务
(a = q.growArray()) != null) { //如果没有空位,那么尝试增长workQueues
//同样获取位置s上的地址偏移量
int j = (((a.length - 1) & s) << ASHIFT) + ABASE;
U.putOrderedObject(a, j, task); //插入任务对象
U.putOrderedInt(q, QTOP, s + 1); //qtop变量加1
submitted = true; //成功提交
}
} finally {
//将这个工作线程对象解锁
U.compareAndSwapInt(q, QLOCK, 1, 0);
}
//如果成功提交任务,那么唤醒这个WorkQueue保存的线程,方法返回结束
if (submitted) {
signalWork(ws, q);
return;
}
}
move = true;
//如果上述WorkQueue为null,并且runState的RSLOCK位为0
} else if (((rs = runState) & RSLOCK) == 0) {
//构造一个INACTIVE类型的WorkQueue,owner为null
q = new WorkQueue(this, null);
q.hint = r; //将r(随机数)赋给hint
q.config = k | SHARED_QUEUE; //将config变量SHARED_QUEUE位设为1
q.scanState = INACTIVE; //将这个WorkQueue设为INACTIVE模式
rs = lockRunState(); //获取runState变量
if (rs > 0 && (ws = workQueues) != null && //线程池正在运行并且工作线程队列不为null
k < ws.length && ws[k] == null) //由随机数产生的k在工作队列上不为null
ws[k] = q; //将新的工作线程对象加入队列
unlockRunState(rs, rs & ~RSLOCK); //将RSLOCK位设为0
} else {
move = true;
}
//重新调整随机数生成器
if (move)
r = ThreadLocalRandom.advanceProbe(r);
}
}
externalSubmit主要任务是构造一个工作线程队列(如果线程池不存在的话),并将任务对象添加到任意一个WorkQueue的任务队列中,然后调用signalWork尝试让工作者线程执行任务。
当一个任务被提交后,singalWork方法会被调用,它的主要任务是在当前线程池没有达到最大线程的情况下添加或者激活一个线程用来执行被提交的任务:
//ws为工作线程队列,q为指定的工作线程
final void signalWork(WorkQueue[] ws, WorkQueue q) {
//c为ctl的值,sp为ctl前32位,i为IdleWorker队列的头结点在workQueues的下标,v为这个IdleWorker,p为这个IdleWorker的parker线程
long c; int sp, i; WorkQueue v; Thread p;
//ctl的值在线程数量没有超出最大线程的情况下为负数(即最后一位始终为1)
while ((c = ctl) < 0L) {
//如果ctl的前32位的值为0,即没有IdleWorker
if ((sp = (int)c) == 0) {
//如果ctl的ADD_WORKER位不为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;
int d = sp - v.scanState;
//将活跃线程计数器加1,并更新IdleWorker相关的值
long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
v.scanState = vs;
//唤醒线程
if ((p = v.parker) != null)
U.unpark(p);
break;
}
if (q != null && q.base == q.top)
break;
}
}
tryAddWorker方法会尝试添加线程并且启动线程,该方法需要传入ctl参数:
private void tryAddWorker(long c) {
boolean add = false; //是否构造一个工作线程
do {
//新的ctl值,将总线程计数器和活跃线程计数器加1
long nc = ((AC_MASK & (c + AC_UNIT)) | (TC_MASK & (c + TC_UNIT)));
//如果ctl值没有其它线程修改,那么继续执行,否则重新获取ctl的值
if (ctl == c) {
int rs, stop;
//判断线程池有没有关闭,如果没有关闭那么更新ctl的值
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;
}
}
//当前线程数量没有达到最大线程数量时
} while (((c = ctl) & ADD_WORKER) != 0L && (int)c == 0);
}
tryAddWorker首先会检测线程池有没有关闭(如果检测到关闭则方法结束),然后会尝试更新ctl的值(将ctl中记录线程数量的区域加1),更新成功后,方法会调用createWorker创造一个工作线程:
private boolean createWorker() {
ForkJoinWorkerThreadFactory fac = factory; //获取线程工厂
Throwable ex = null; //创造线程时抛出的异常
ForkJoinWorkerThread wt = null; //由线程工厂生产的线程
try {
//创造一个新的线程并运行,返回true
if (fac != null && (wt = fac.newThread(this)) != null) {
wt.start();
return true;
}
} catch (Throwable rex) {
ex = rex;
}
//如果在构造线程时有任何异常抛出,那么从线程池取消这个线程
deregisterWorker(wt, ex);
return false;
}
createWorker方法负责通过ForkJoinPool指定的线程工厂构造一个ForkJoinWorkerThread实例,并启动这个线程。
在ForkJoinWorkThread的构造方法中,会调用ForkJoinPool的registerWorker方法将当前线程注册到ForkJoinPool中:
final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
UncaughtExceptionHandler handler;
//将wt设为后台线程
wt.setDaemon(true);
//如果未捕获异常处理器不为null,那么设置该线程的未捕获异常处理器
if ((handler = ueh) != null)
wt.setUncaughtExceptionHandler(handler);
//构造一个WorkQueue,owner变量为线程为wt
WorkQueue w = new WorkQueue(this, wt);
int i = 0;
//获取workQueues属于LIFO还是FIFO
int mode = config & MODE_MASK;
//加锁,并返回runState
int rs = lockRunState();
try {
WorkQueue[] ws; int n;
//如果workQueues不为null并且workQueues的长度大于0
if ((ws = workQueues) != null && (n = ws.length) > 0) {
int s = indexSeed += SEED_INCREMENT;
int m = n - 1;
//该WorkQueue插入到workQueues的位置(保证是奇数位)
i = ((s << 1) | 1) & m;
//如果这个位置已经有其它WorkQueue占有
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;
}
}
}
w.hint = s; //将indexSeed作为hint
w.config = i | mode; //将下标和模式保存到config
w.scanState = i; //直接下标作为scanState的值
ws[i] = w; //最后插入w到workQueues
}
} finally {
//解锁
unlockRunState(rs, rs & ~RSLOCK);
}
//设置线程的名称
wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1)));
return w;
}
registerWorker方法会构造一个ACTIVE类型的WorkQueue并插入到workQueues数组的一个奇数位中。
如果在构造线程时有任何异常发生,那么调用deregisterWorker从线程池中移除这个线程:
final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) {
WorkQueue w = null; //保存线程对象的workQueue
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)
ws[idx] = null;
//释放锁
unlockRunState(rs, rs & ~RSLOCK);
}
long c;
//不断尝试将ctl中保存线程池数量的位减去1
do {} while (!U.compareAndSwapLong(this, CTL, c = ctl, ((AC_MASK & (c - AC_UNIT)) |
(TC_MASK & (c - TC_UNIT)) | (SP_MASK & c))));
if (w != null) {
w.qlock = -1;
//更新ForkJoinPool的stealCount变量(将这个工作线程对象的nsteals变量加上)
w.transferStealCount(this);
//取消这个任务队列所有的任务
w.cancelAll();
}
for (;;) {
WorkQueue[] ws; int m, sp;
if (tryTerminate(false, false) || //检查线程池是否关闭,如果正在关闭那么协助其它线程,返回true,否则返回false
w == null || w.array == null || (runState & STOP) != 0 || (ws = workQueues) == null || (m = ws.length - 1) < 0) //检查参数
break;
if ((sp = (int)(c = ctl)) != 0) {
if (tryRelease(c, ws[sp & m], AC_UNIT))
break;
} else if (ex != null && (c & ADD_WORKER) != 0L) {
tryAddWorker(c);
break;
} else break;
}
if (ex == null)
ForkJoinTask.helpExpungeStaleExceptions();
else
ForkJoinTask.rethrow(ex);
}
线程调用start正常启动后,会执行ForkJoinWorkThread的run方法:
public void run() {
if (workQueue.array == null) {
Throwable exception = null;
try {
onStart(); //用于子类重写的方法,默认实现为空
//执行任务
pool.runWorker(workQueue);
//捕获执行任务期间未捕获异常
} catch (Throwable ex) {
exception = ex;
} finally {
try {
//用于子类重写的方法,默认实现为空,需要传入未捕获的异常(可以为null)
onTermination(exception);
} catch (Throwable ex) {
if (exception == null)
exception = ex;
} finally {
//从线程池解除这个线程
pool.deregisterWorker(this, exception);
}
}
}
}
线程会调用线程池的runWorker方法并传入这个线程所属的WorkQueue对象:
final void runWorker(WorkQueue w) {
//检查任务队列空间是否足够,不够的话进行扩容
w.growArray();
//以hint的值作为准备窃取的队列位置
int seed = w.hint;
int r = (seed == 0) ? 1 : seed;
for (ForkJoinTask<?> t;;) {
//尝试窃取其它工作线程对象管理的任务队列中的任务,如果返回值不为null,那么调用runTask执行任务
if ((t = scan(w, r)) != null)
w.runTask(t);
//如果没有窃取到任务,那么调用awaitWork方法,如果awaitWork方法返回false,那么该线程会被终止并从线程池中移除
else if (!awaitWork(w, r))
break;
r ^= r << 13; r ^= r >>> 17; r ^= r << 5;
}
}
runWorker方法首先会尝试调用scan方法窃取别的WorkQueue所管理的任务队列(即工作线程队列的hint位置的WorkQueue.array),如果窃取成功,那么会调用其runTask方法执行窃取到的任务,否则,调用awaitWork方法让当前线程等待任务。
我们先从scan方法开始分析:
private ForkJoinTask<?> scan(WorkQueue w, int r) {
//ws为workQueues,m为workQueues的长度减1
WorkQueue[] ws; int m;
if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
int ss = w.scanState; //获取参数w的scanState变量
//从workQueues位于r的WorkQueue开始,origin为本次循环的WorkQueue在workQueues中的下标
for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
//q为本次循环的WorkQueue对象,a为q的任务队列,t为q的任务队列中base位置的任务
WorkQueue q; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
//b为q的base变量的值,n为q的base - top变量的值,c为线程池的变量ctl的值
int b, n; long c;
if ((q = ws[k]) != null) { //确保q在任务队列中存在
//如果q的任务队列中存在未执行的任务
if ((n = (b = q.base) - q.top) < 0 && (a = q.array) != null) {
//q的任务队列中base位置的任务对象的地址偏移量
long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
//根据地址偏移量获取base位置的任务队列,如果不为null并且base变量没有被别的线程更改
if ((t = ((ForkJoinTask<?>) U.getObjectVolatile(a, i))) != null && q.base == b) {
if (ss >= 0) { //如果q属于ACTIVE类型的WorkQueue
//尝试从任务队列中移除这个任务
if (U.compareAndSwapObject(a, i, t, null)) {
q.base = b + 1;
//如果n小于-1,那么唤醒q中的线程
if (n < -1)
signalWork(ws, q);
//返回窃取到的任务对象t
return t;
}
//如果q属于INACTIVE类型的WorkQueue,那么唤醒其中一个线程
} else if (oldSum == 0 && w.scanState < 0)
tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
}
if (ss < 0) //如果ss小于0,那么重新获取scanState的值
ss = w.scanState;
//重新为origin赋值,作为下一次循环的WorkQueue
r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
origin = k = r & m;
oldSum = checkSum = 0;
continue;
}
checkSum += b;
}
//如果遍历了所有的WorkQueue
if ((k = (k + 1) & m) == origin) {
if ((ss >= 0 || (ss == (ss = w.scanState))) && oldSum == (oldSum = checkSum)) {
//如果这个任务线程已经停止,那么停止循环返回null
if (ss < 0 || w.qlock < 0)
break;
int ns = ss | INACTIVE; //将scanState标记为INACTIVE
//将ctl的ACTIVE记录位减1,并保存其INACTIVE属性
long nc = ((SP_MASK & ns) | (UC_MASK & ((c = ctl) - AC_UNIT)));
//将ctl的前32位的值赋给stackPred变量,表示Idle Worker栈的头结点为当前线程的WorkQueue
w.stackPred = (int)c;
//将ns的值作为w的scanState的新值
U.putInt(w, QSCANSTATE, ns);
//尝试更新ctl的值为nc,如果成功则将ns的值赋给ss,否则仍然保留ss的值作为scanState
if (U.compareAndSwapLong(this, CTL, c, nc))
ss = ns;
else
w.scanState = ss;
}
checkSum = 0;
}
}
}
return null;
}
scan方法从workQueues数组的hint % array.length对应的位置开始,如果这个位置存在WorkQueue,那么会尝试从这个WorkQueue的任务队列的base位置窃取一个任务并返回(如果这个WorkQueue存在尚未执行的任务)。如果遍历完所有的WorkQueue仍然没有窃取到任务,那么尝试将当前线程对应的WorkQueue的scanState的INACTIVE位置为1,并将这个WorkQueue在workQueues数组的下标作为stackPred的值,随后,再更新ctl的值,并将这个WorkQueue作为Idle Worker栈的头WorkQueue。
窃取到任务后,WorkQueue的runTask方法会被执行并以这个窃取到的任务作为参数传入:
final void runTask(ForkJoinTask<?> task) {
if (task != null) {
scanState &= ~SCANNING; //标记为繁忙状态
//将这个窃取到的任务赋给currentSteal,并调用doExec执行任务逻辑
(currentSteal = task).doExec();
//任务执行完毕,将currentSteal设为null
U.putOrderedObject(this, QCURRENTSTEAL, null);
//执行自己的任务队列中包含的任务
execLocalTasks();
ForkJoinWorkerThread thread = owner;
//如果nsteals越界,那么将nsteals加到stealCounter计数器
if (++nsteals < 0)
transferStealCount(pool);
//标记为非繁忙状态
scanState |= SCANNING;
if (thread != null)
thread.afterTopLevelExec(); //该方法目前实现为空,不用理会
}
}
runTask方法在调用ForkJoinTask的doExec方法执行任务,doExec方法会调用exec方法来执行子类定义的任务逻辑。执行完成后,execLocalTasks方法会被调用来执行当前WorkQueue包含的任务:
final void execLocalTasks() {
int b = base, m, s;
ForkJoinTask<?>[] a = array;
//如果任务队列包含任务
if (b - (s = top - 1) <= 0 && a != null && (m = a.length - 1) >= 0) {
//如果是FIFO模式
if ((config & FIFO_QUEUE) == 0) {
for (ForkJoinTask<?> t;;) {
//尝试取出(不是获取)array[(top-1)%array.length]位置的任务,如果任务为null,则退出循环,方法结束
if ((t = (ForkJoinTask<?>)U.getAndSetObject(a, ((m & s) << ASHIFT) + ABASE, null)) == null)
break;
U.putOrderedInt(this, QTOP, s); //将s赋给top
t.doExec(); //执行任务
//如果没有任务可取了,那么退出循环
if (base - (s = top - 1) > 0)
break;
}
//如果队列为空,或者是LIFO模式
} else
pollAndExecAll();
}
}
final void pollAndExecAll() {
//从base位取出任务并执行,直到没有任务可执行
for (ForkJoinTask<?> t; (t = poll()) != null;)
t.doExec();
}
如果之前scan方法执行结束依然没有窃取到任务,那么awaitWork方法会被调用:
private boolean awaitWork(WorkQueue w, int r) {
if (w == null || w.qlock < 0) //如果线程池已被关闭
return false;
//获取w的前驱Idle Worker,将spins设为0
for (int pred = w.stackPred, spins = SPINS, ss;;) {
//如果是ACTIVE的WorkQueue,那么方法退出返回true
if ((ss = w.scanState) >= 0)
break;
//这里在JDK1.8中不会执行
else if (spins > 0) {
r ^= r << 6; r ^= r >>> 21; r ^= r << 7;
if (r >= 0 && --spins == 0) {
WorkQueue v; WorkQueue[] ws; int s, j; AtomicLong sc;
if (pred != 0 && (ws = workQueues) != null && (j = pred & SMASK) < ws.length &&
(v = ws[j]) != null && (v.parker == null || v.scanState >= 0))
spins = SPINS;
}
}
//如果当前WorkQueue已经停止,返回false
else if (w.qlock < 0)
return false;
//如果线程没有中断
else if (!Thread.interrupted()) {
//c为ctl的值,prevctl为ctl的新值
long c, prevctl, parkTime, deadline;
//获取当前线程池的ACTIVE类型的WorkQueue的数量
int ac = (int)((c = ctl) >> AC_SHIFT) + (config & SMASK);
//如果线程池正在关闭,返回false
if ((ac <= 0 && tryTerminate(false, false)) || (runState & STOP) != 0)
return false;
//如果数量小于等于0,并且是IdleWorker栈顶的WorkQueue
if (ac <= 0 && ss == (int)c) {
//将ctl的ACTIVE计数器加1,将w的stackPred对应的WorkQueue作为IdleWorker栈顶
prevctl = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & pred);
//将WorkQueue的总数赋给t
int t = (short)(c >>> TC_SHIFT);
//如果总数大于2并且更新ctl的值成功,那么返回false
if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl))
return false;
//将线程休眠时间设为2*|t|
parkTime = IDLE_TIMEOUT * ((t >= 0) ? 1 : 1 - t);
//设置线程休眠截止时间
deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
}
else
prevctl = parkTime = deadline = 0L;
Thread wt = Thread.currentThread();
//将当前线程对象的parkBlocker引用设为当前线程池,用于LockSupport
U.putObject(wt, PARKBLOCKER, this);
//将parker设为当前线程
w.parker = wt;
//如果w属于INACTIVE类型并且ctl的值没有被更改,那么将当前线程休眠parkTime纳秒
if (w.scanState < 0 && ctl == c)
U.park(false, parkTime);
//将w的park设为null,同时也将当前线程的parkBlocker设为null
U.putOrderedObject(w, QPARKER, null);
U.putObject(wt, PARKBLOCKER, null);
//再次检查w的scanState
if (w.scanState >= 0)
break;
//如果设置了休眠时间并且ctl没有被其它线程修改并且休眠已经超时并且成功更新ctl的值,返回false
if (parkTime != 0L && ctl == c && deadline - System.nanoTime() <= 0L &&
U.compareAndSwapLong(this, CTL, c, prevctl))
return false;
}
}
return true;
}
关闭线程池有两种方法:shutdown方法和shutdownNow方法:
public void shutdown() {
checkPermission();
tryTerminate(false, true);
}
public List<Runnable> shutdownNow() {
checkPermission();
tryTerminate(true, true);
return Collections.emptyList();
}
这两个方法都会首先调用checkPermission方法检查调用者是否有权限关闭线程池,权限认证通过后,就会调用tryTerminate方法关闭线程池:
private boolean tryTerminate(boolean now, boolean enable) {
int rs;
if (this == common) //不允许关闭common pool
return false;
//如果线程池尚未调用过shutdown方法
if ((rs = runState) >= 0) {
if (!enable)
return false;
//对runState变量加锁
rs = lockRunState();
//将SHUTDOWN位标记为1,这样runState始终小于0
unlockRunState(rs, (rs & ~RSLOCK) | SHUTDOWN);
}
if ((rs & STOP) == 0) { //如果STOP位为0
if (!now) { //如果调用的是shutdown方法
for (long oldSum = 0L;;) { // repeat until stable
WorkQueue[] ws; WorkQueue w; int m, b; long c;
long checkSum = ctl;
//如果active Worker大于最大线程数,返回false
if ((int)(checkSum >> AC_SHIFT) + (config & SMASK) > 0)
return false;
//如果任务队列为空跳出循环
if ((ws = workQueues) == null || (m = ws.length - 1) <= 0)
break;
//遍历workQueues数组
for (int i = 0; i <= m; ++i) {
if ((w = ws[i]) != null) {
//如果本次循环的WorkQueue的base不等于top或者该WorkQueue为ACTIVE或者正在执行窃取任务
if ((b = w.base) != w.top || w.scanState >= 0 || w.currentSteal != null) {
//unpark本次循环的WorkQueue的parker线程,返回false
tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
return false;
}
checkSum += b;
//如果该WorkQueue是ACTIVE类型的,那么将qlock设为-1,达到通知线程池关闭的作用
if ((i & 1) == 0)
w.qlock = -1;
}
}
//如果oldSum等于checkSum,那么跳出循环
if (oldSum == (oldSum = checkSum))
break;
}
}
//再次检查runState的STOP位,如果STOP位依旧为0,那么修改为1
if ((runState & STOP) == 0) {
rs = lockRunState();
unlockRunState(rs, (rs & ~RSLOCK) | STOP);
}
}
int pass = 0; //表示步骤
for (long oldSum = 0L;;) {
WorkQueue[] ws; WorkQueue w; ForkJoinWorkerThread wt; int m;
long checkSum = ctl;
//如果total count小于最大线程数量或者workQueues为空
if ((short)(checkSum >>> TC_SHIFT) + (config & SMASK) <= 0 ||
(ws = workQueues) == null || (m = ws.length - 1) <= 0) {
//如果当前线程池TERMINATED位为0
if ((runState & TERMINATED) == 0) {
//将runState标记为线程池彻底关闭
rs = lockRunState();
unlockRunState(rs, (rs & ~RSLOCK) | TERMINATED);
//激活调用awaitTermination中等待的线程
synchronized (this) { notifyAll(); }
}
break;
}
for (int i = 0; i <= m; ++i) { //遍历workQueues数组
if ((w = ws[i]) != null) {
checkSum += w.base;
w.qlock = -1; //将该WorkQueue标记为不可用
if (pass > 0) { //如果不是第1步
w.cancelAll(); //取消所有任务
if (pass > 1 && (wt = w.owner) != null) { //如果到了第2、3步,并且owner不为null
if (!wt.isInterrupted()) { //如果owner没有中断
try {
wt.interrupt(); //尝试终端线程
} catch (Throwable ignore) { } //忽略异常
}
if (w.scanState < 0)
U.unpark(wt);
}
}
}
}
if (checkSum != oldSum) {
oldSum = checkSum;
pass = 0;
//如果已经遍历完workQueues
} else if (pass > 3 && pass > m)
break;
//增加pass,如果此时大于1
else if (++pass > 1) {
long c; int j = 0, sp;
//释放所有的parker
while (j++ <= m && (sp = (int)(c = ctl)) != 0)
tryRelease(c, ws[sp & m], AC_UNIT);
}
}
return true;
}
了解完ForkJoinPool后,我们再来从ForkJoinTask角度去理解Fork/Join框架
ForkJoinTask仅有一个实例变量:status,它是一个volatile修饰的int变量,其可能的状态为:
常量名 | 常量值 | 作用 |
---|---|---|
DONE_MASK | 0xF0000000 | 表示保存任务状态的位 |
NORMAL | 0xF0000000 | 当前任务已完成 |
CANCELLED | 0xC0000000 | 当前任务被取消 |
EXCEPTIONAL | 0x80000000 | 当前任务抛出未捕获的异常 |
SIGNAL | 0x00010000 | 标记位 |
SMASK | 0x0000FFFF | 任务的标签数 |
当任务执行结束时,status变量会小于0
fork方法会尝试将this添加到线程池中异步执行,通常是使用在exec方法中new出来的子任务。
public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
fork方法首先会判断当前线程是否是ForkJoinWorkerThread,如果属于ForkJoinWorkerThread则说明是线程池中的线程调用的该方法,会尝试将任务添加到该ForkJoinWorkerThread所属的WorkQueue中。否则,则会尝试将任务添加到common线程池中(common线程池是ForkJoinPool的实例,在ForkJoinPool的类加载期间会实例化一个ForkJoinPool作为common线程池,相当于一个默认的ForkJoinPool)。
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
join方法首先会调用doJoin方法,doJoin方法会尝试获取任务执行结果,如果检测到该任务还尚未被线程池执行,那么doJoin方法会尝试从当前线程所属的任务队列中取出任务并尝试执行,然后返回status变量。如果线程池正在执行或执行完毕,那么调用ForkJoinPool的awaitJoin方法获取任务结果:
private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
//如果任务执行结束,直接返回status
if((s = status) < 0)
return s;
//如果当前线程属于ForkJoinWorkerThread
if((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
wt = (ForkJoinWorkerThread)t;
w = wt.workQueue;
//尝试从队列中取出this,取出成功后,执行任务
if(w.tryUnpush(this) && (s = doExec()) < 0)
return s;
//等待任务执行结束(该方法也可能会尝试帮助其它线程完成任务)
return wt.pool.awaitJoin(w, this, 0L);
}
//等待任务执行结束
return externalAwaitDone();
}
其中,doExec方法会调用抽象方法exec执行任务:
final int doExec() {
int s; boolean completed;
if ((s = status) >= 0) { //判断任务有没有被执行完成
try {
completed = exec();
//如果执行期间有未捕获的异常
} catch (Throwable rex) {
return setExceptionalCompletion(rex);
}
//任务正常完成
if (completed)
s = setCompletion(NORMAL);
}
return s;
}
如果exec方法执行期间有未捕获的异常,那么setExceptionalCompletion会将异常信息保存到ForkJoinTask类:
private int setExceptionalCompletion(Throwable ex) {
int s = recordExceptionalCompletion(ex);
if ((s & DONE_MASK) == EXCEPTIONAL)
internalPropagateException(ex);
return s;
}
ForkJoinTask类维护了一个哈希表,该哈希表基于拉链法实现,它负责保存ForkJoinTask实例抛出的未捕获异常,这样做可以很方便地获取到每个任务抛出的未捕获异常:
private static final ExceptionNode[] exceptionTable; //保存异常信息的哈希表,长度为32
private static final ReentrantLock exceptionTableLock; //自旋锁,保证哈希表的线程安全
private static final ReferenceQueue<Object> exceptionTableRefQueue; //引用队列
异常信息通过结点ExceptionNode保存:
static final class ExceptionNode extends WeakReference<ForkJoinTask<?>> {
final Throwable ex; //任务执行期间抛出的未捕获异常
ExceptionNode next; //下一个异常结点,用于解决哈希冲突
final long thrower; //执行该任务的线程ID
final int hashCode; //哈希值
ExceptionNode(ForkJoinTask<?> task, Throwable ex, ExceptionNode next) {
super(task, exceptionTableRefQueue); //将任务对象通过弱引用保存,防止内存泄漏
this.ex = ex;
this.next = next;
this.thrower = Thread.currentThread().getId();
this.hashCode = System.identityHashCode(task);
}
}
了解这些后,我们再来看recordExceptionalCompletion是怎样实现的:
final int recordExceptionalCompletion(Throwable ex) {
int s;
if ((s = status) >= 0) {
//获取当前任务对象的hashCode
int h = System.identityHashCode(this);
final ReentrantLock lock = exceptionTableLock;
lock.lock();
try {
//清除废弃的ExceptionNode(即失去引用并且没有去获取异常的ForkJoinTask)
expungeStaleExceptions();
ExceptionNode[] t = exceptionTable;
int i = h & (t.length - 1);
//遍历这个桶
for (ExceptionNode e = t[i]; ; e = e.next) {
//如果这个位置为null,那么构造一个ExceptionNode保存异常对象
if (e == null) {
t[i] = new ExceptionNode(this, ex, t[i]);
break;
}
if (e.get() == this)
break;
}
} finally {
lock.unlock();
}
//将status标记为异常完成
s = setCompletion(EXCEPTIONAL);
}
return s;
}
回到doJoin方法:
doJoin方法结束后,会返回status变量,status变量可以判断任务执行情况。如果任务正常完成,那么会调用getRawResult返回结果,方法结束。如果任务执行期间抛出未捕获异常或者被取消,那么reportException方法会被调用:
private void reportException(int s) {
if (s == CANCELLED)
throw new CancellationException();
if (s == EXCEPTIONAL)
rethrow(getThrowableException());
}
如果任务被取消,那么此时会抛出CancellationException异常,如果是因为任务执行期间抛出了未捕获异常,那么会调用getThrowableException从异常表中获取这个异常,最后调用rethrow将这个异常包装为RuntimeException并抛出它。