关于框架fork-join的概述网上很多,本文深入剖析java平台下fork-join的实现。
作为一个轻量级的并发执行框架,fork-join事实上由3个角色构成:任务队列(WorkQueue)、工作者线程(ForkJoinWorkerThread)、任务(ForkJoinTask),他们一般通过执行者(ForkJoinPool)的接口来对外提供服务。
对于这些角色如何协调来执行任务,我们通过三个方面进行阐述:提交任务、执行任务、联结任务(join)。
提交任务
当你实例化一个ForkJoinPool之后,一般有三种提交任务的方式:execute、submit(返回future)、invoke(返回join操作得到的结果)。
他们都调用externalPush方法:
final void externalPush(ForkJoinTask<?> task) {
WorkQueue q; int m, s, n, am; ForkJoinTask<?>[] a;
int r = ThreadLocalRandom.getProbe();
int ps = plock;
WorkQueue[] ws = workQueues;
if (ps > 0 && ws != null && (m = (ws.length - 1)) >= 0 &&
(q = ws[m & r & SQMASK]) != null && r != 0 &&
U.compareAndSwapInt(q, QLOCK, 0, 1)) { // lock
if ((a = q.array) != null &&
(am = a.length - 1) > (n = (s = q.top) - q.base)) {
int j = ((am & s) << ASHIFT) + ABASE;
U.putOrderedObject(a, j, task);
q.top = s + 1; // push on to deque
q.qlock = 0;
if (n <= 1)
signalWork(ws, q);
return;
}
q.qlock = 0;
}
fullExternalPush(task);
}
02-05行:分别取得探测数r(线程私有的threadLocalRandomProbe)、ps(执行器私有的plock)、ws(执行器私有的任务队列)。
06-20行:当执行器没有停止&&任务队列不为空&&通过探测数取得的某个even队列不为空&&成功锁定了这个任务队列之后,假如队列至少剩余2个空位时,将task放入top指向的位置,更新top值,并且释放队列锁,假如之前的队列中任务数少于或者等于1就调用signalWork,返回。假如队列不满足剩余2个空位(释放锁)或者ws不满足条件,直接进入21行。
21行:调用fullExternalPush来进行另一层面的提交任务。
要点:事实上大多数提交任务只需要执行这部分代码,除了第一次提交和少数workQueue还未初始化的情况。这里有趣的地方在于,因为操作执行的时间极短,同一个queue时的同步不是通过传统的锁来处理了,而是通过CAS操作来改变私用的qlock的值来实现互斥操作,1为锁定(1: locked, -1: terminate; else 0),失败之后就转为进入fullExternalPush。另外对于执行器来说,ps<0代表执行器被关闭,SQMASK[0X7E]即126用于取得二进制下第一位为0的队列,就是事实上的共享队列,另外探测数不能为0。
fullExternalPush:
private void fullExternalPush(ForkJoinTask<?> task) {
int r;
if ((r = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit();
r = ThreadLocalRandom.getProbe();
}
for (;;) {
WorkQueue[] ws; WorkQueue q; int ps, m, k;
boolean move = false;
if ((ps = plock) < 0)
throw new RejectedExecutionException();
else if (ps == 0 || (ws = workQueues) == null ||
(m = ws.length - 1) < 0) { // initialize workQueues
int p = parallelism; // find power of two table size
int n = (p > 1) ? p - 1 : 1; // ensure at least 2 slots
n |= n >>> 1; n |= n >>> 2; n |= n >>> 4;
n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1;
WorkQueue[] nws = ((ws = workQueues) == null || ws.length == 0 ?
new WorkQueue[n] : null);
if (((ps = plock) & PL_LOCK) != 0 ||
!U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
ps = acquirePlock();
if (((ws = workQueues) == null || ws.length == 0) && nws != null)
workQueues = nws;
int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN);
if (!U.compareAndSwapInt(this, PLOCK, ps, nps))
releasePlock(nps);
}
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 { // locked version of push
if ((a != null && a.length > s + 1 - q.base) ||
(a = q.growArray()) != null) { // must presize
int j = (((a.length - 1) & s) << ASHIFT) + ABASE;
U.putOrderedObject(a, j, task);
q.top = s + 1;
submitted = true;
}
} finally {
q.qlock = 0; // unlock
}
if (submitted) {
signalWork(ws, q);
return;
}
}
move = true; // move on failure
}
else if (((ps = plock) & PL_LOCK) == 0) { // create new queue
q = new WorkQueue(this, null, SHARED_QUEUE, r);
q.poolIndex = (short)k;
if (((ps = plock) & PL_LOCK) != 0 ||
!U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
ps = acquirePlock();
if ((ws = workQueues) != null && k < ws.length && ws[k] == null)
ws[k] = q;
int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN);
if (!U.compareAndSwapInt(this, PLOCK, ps, nps))
releasePlock(nps);
}
else
move = true; // move if busy
if (move)
r = ThreadLocalRandom.advanceProbe(r);
}
}
03-06行:取得探测数r,当为0时重新计算并得到非0探测数。
08-28行:设置move(用于判断是否需要重设探测数),得到plock(假如为负就直接抛出RejectedExecutionException异常拒绝),接下来通过并行数(parallelism)来构造我们的队列组,得到一个合适的队列组大小n,获得锁plock(即把第2位从0变为1),设置队列组,释放锁(第2位1变为0),进入下一次循环。
29-50行:通过r取得的queue不为空,并且取得锁(qlock)之后将任务放进array里,如有需要会扩充array。成功之后释放所,并且给出一个信号signalWork,然后返回。假如queue不为空但是竞争锁失败,那么把move变为true,进入下一次循环。
52-62行:通过r取得的queue为空,那么就创建一个共享queue(SHARED_QUEUE),之后获取执行器的锁对象(plock),假如为空则在这个下标下设置queue,释放锁,进入下一次循环。
65行:连创建queue的机会都被别的取走,那么便改变move。
66-67行:如果move被设置,那么重新计算r的值。
要点:事实上这里可以分为3个阶段的操作,第一阶段创建queue组,第二阶段创建以r计算得到的下标对应的queue实例,第三阶段通过得到的不为空的queue实例来push一个task(有可能需要创建为空的array)。当然,最后还是会通过提交任务从而退出,退出前传递信号signalWork。在需要的时候设置move为true从而选择其他的queue组下标,比如在选择的queue被其他线程锁定或者想要创建queue实例却被其他线程抢先一步,从而提高效率。另一方面,这里创建的共享队列组的个数与parallelism相同,并且workQueues的总数为2*parallelism(下标第一位0的为共享队列,1为工作队列),这个构造减少了任务队列的争用情况,并且提前构造了足够多的队列引用。另外这里有趣的是加锁的方式:自旋+内置锁,大多数情况下会通过自旋锁的方式得到锁,内置锁作为一种备份选择实际上只是为了进入等待状态,通过CAS将plock用PL_LOCK把第2位变为1从而锁定,第1位变为1从而表明有线程处于等待状态,解锁时候将plock第二位变回为0,需要的话会获取内置锁从而唤醒等待线程,整个过程需要保留处于第32位用来标示关闭的SHUTDOWN,具体可以参考acquirePlock和releasePlock。(acquirePlock中存在理论上的ABA问题,后面会讲)
final void signalWork(WorkQueue[] ws, WorkQueue q) {
for (;;) {
long c; int e, u, i; WorkQueue w; Thread p;
if ((u = (int)((c = ctl) >>> 32)) >= 0)
break;
if ((e = (int)c) <= 0) {
if ((short)u < 0)
tryAddWorker();
break;
}
if (ws == null || ws.length <= (i = e & SMASK) ||
(w = ws[i]) == null)
break;
long nc = (((long)(w.nextWait & E_MASK)) |
((long)(u + UAC_UNIT)) << 32);
int ne = (e + E_SEQ) & E_MASK;
if (w.eventCount == (e | INT_SIGN) &&
U.compareAndSwapLong(this, CTL, c, nc)) {
w.eventCount = ne;
if ((p = w.parker) != null)
U.unpark(p);
break;
}
if (q != null && q.base >= q.top)
break;
}
}
这个方法发送一个信号:添加了一个任务。目的:可能需要增加一个活动工作线程。工作方式如下:
- 假如活动线程数已满((u=(int)((c=ctl)>>>32))>=0),不需要做什么,break;
- 假如执行器已经停止或者没有处于等待状态的工作线程((e=(int)c)<=0),当总线程数未满((e=(int)c)<=0)试着添加一个工作线程,break;
- 试着唤醒一个等待的工作线程,成功break,失败判断q中的任务数后决定是否重来;(ctl中1-31位和WorkQueue中的nextWait协作实现了等待线程的Treiber stack)
private void tryAddWorker() {
long c; int u, e;
while ((u = (int)((c = ctl) >>> 32)) < 0 &&
(u & SHORT_SIGN) != 0 && (e = (int)c) >= 0) {
long nc = ((long)(((u + UTC_UNIT) & UTC_MASK) |
((u + UAC_UNIT) & UAC_MASK)) << 32) | (long)e;
if (U.compareAndSwapLong(this, CTL, c, nc)) {
ForkJoinWorkerThreadFactory fac;
Throwable ex = null;
ForkJoinWorkerThread wt = null;
try {
if ((fac = factory) != null &&
(wt = fac.newThread(this)) != null) {
wt.start();
break;
}
} catch (Throwable rex) {
ex = rex;
}
deregisterWorker(wt, ex);
break;
}
}
}
这个方法试着添加一个工作线程。工作方式如下:
- 假如活动工作线程数未满&&工作线程数未满&&执行器未停止,试着增加一个工作线程(ctl更新失败会重试),启动线程,break;假如出异常,记录异常信息撤销线程,break;
- newThread默认会创建一个ForkJoinWorkerThread对象;
执行任务
我们从ForkJoinWorkerThread的run方法开始:
public void run() {
if (workQueue.array == null) { // only run once
Throwable exception = null;
try {
onStart();
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);
}
}
}
}
工作方式如下:
- 工作线程会有一个伴随的工作队列(当然不会是共享任务队列)初始的array为空,调用执行器的runWorker以及workQueue来执行任务。结束时候撤销当前工作线程。
final void runWorker(WorkQueue w) {
w.growArray(); // allocate queue
for (int r = w.hint; scan(w, r) == 0; ) {
r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
}
}
- 扩充当前线程对象的workqueue,进入扫描任务队列的scan方法,附带一个随机r数(初始为steal提示数),当返回非0时结束当前线程。
private final int scan(WorkQueue w, int r) {
WorkQueue[] ws; int m;
long c = ctl; // for consistency check
if ((ws = workQueues) != null && (m = ws.length - 1) >= 0 && w != null) {
for (int j = m + m + 1, ec = w.eventCount;;) {
WorkQueue q; int b, e; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
if ((q = ws[(r - j) & m]) != null &&
(b = q.base) - q.top < 0 && (a = q.array) != null) {
long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
if ((t = ((ForkJoinTask<?>)
U.getObjectVolatile(a, i))) != null) {
if (ec < 0)
helpRelease(c, ws, w, q, b);
else if (q.base == b &&
U.compareAndSwapObject(a, i, t, null)) {
U.putOrderedInt(q, QBASE, b + 1);
if ((b + 1) - q.top < 0)
signalWork(ws, q);
w.runTask(t);
}
}
break;
}
else if (--j < 0) {
if ((ec | (e = (int)c)) < 0) // inactive or terminating
return awaitWork(w, c, ec);
else if (ctl == c) { // try to inactivate and enqueue
long nc = (long)ec | ((c - AC_UNIT) & (AC_MASK|TC_MASK));
w.nextWait = e;
w.eventCount = ec | INT_SIGN;
if (!U.compareAndSwapLong(this, CTL, c, nc))
w.eventCount = ec; // back out
}
break;
}
}
}
return 0;
}
工作线程执行的主要任务。工作方式如下:
- 当任务队列组不为空时,对任务组做两遍扫描,假如发现某个任务队列不为空,如果当前线程处于等待状态,唤醒一个工作线程(helpRelease),break;如果能够取得任务,那么便执行该任务(runTask),break;
- 假如当前工作线程处于等待状态,进入可能阻塞当前线程的awaitWork,之后返回数字;
- 假如ctl稳定,试着递减ctl中活动线程的个数并且将当前线程改变为等待状态(线程状态位INT_SIGN,入Treiber栈),若失败则递增回来,break
private final void helpRelease(long c, WorkQueue[] ws, WorkQueue w,
WorkQueue q, int b) {
WorkQueue v; int e, i; Thread p;
if (w != null && w.eventCount < 0 && (e = (int)c) > 0 &&
ws != null && ws.length > (i = e & SMASK) &&
(v = ws[i]) != null && ctl == c) {
long nc = (((long)(v.nextWait & E_MASK)) |
((long)((int)(c >>> 32) + UAC_UNIT)) << 32);
int ne = (e + E_SEQ) & E_MASK;
if (q != null && q.base == b && w.eventCount < 0 &&
v.eventCount == (e | INT_SIGN) &&
U.compareAndSwapLong(this, CTL, c, nc)) {
v.eventCount = ne;
if ((p = v.parker) != null)
U.unpark(p);
}
}
}
这个方法尝试唤醒一个等待线程,工作方式如下:
- 取出ctl中栈中最新等待线程,当这个等待线程状态为等待状态&&探测的队列的base指标不变&&当前线程状态为等待状态&&改变ctl成功,唤醒等待线程以及改变其eventCount。
final void runTask(ForkJoinTask<?> task) {
if ((currentSteal = task) != null) {
ForkJoinWorkerThread thread;
task.doExec();
ForkJoinTask<?>[] a = array;
int md = mode;
++nsteals;
currentSteal = null;
if (md != 0)
pollAndExecAll();
else if (a != null) {
int s, m = a.length - 1;
ForkJoinTask<?> t;
while ((s = top - 1) - base >= 0 &&
(t = (ForkJoinTask<?>)U.getAndSetObject
(a, ((m & s) << ASHIFT) + ABASE, null)) != null) {
top = s;
t.doExec();
}
}
if ((thread = owner) != null) // no need to do in finally clause
thread.afterTopLevelExec();
}
}
这个方法用于执行任务,工作方式如下:
- 将取得的task设置为currentSteal,并且不为空时,执行该task,将currentSteal重置为空。
- 根据当前的mode模式(LIFO/FIFO),从私有队列里面取得task来执行(doExec)。
- 当执行完私有队列里面的任务时,调用当前线程的afterTopLevelExec方法,默认为空。
private final int awaitWork(WorkQueue w, long c, int ec) {
int stat, ns; long parkTime, deadline;
if ((stat = w.qlock) >= 0 && w.eventCount == ec && ctl == c &&
!Thread.interrupted()) {
int e = (int)c;
int u = (int)(c >>> 32);
int d = (u >> UAC_SHIFT) + parallelism; // active count
if (e < 0 || (d <= 0 && tryTerminate(false, false)))
stat = w.qlock = -1; // pool is terminating
else if ((ns = w.nsteals) != 0) { // collect steals and retry
w.nsteals = 0;
U.getAndAddLong(this, STEALCOUNT, (long)ns);
}
else {
long pc = ((d > 0 || ec != (e | INT_SIGN)) ? 0L :
((long)(w.nextWait & E_MASK)) | // ctl to restore
((long)(u + UAC_UNIT)) << 32);
if (pc != 0L) { // timed wait if last waiter
int dc = -(short)(c >>> TC_SHIFT);
parkTime = (dc < 0 ? FAST_IDLE_TIMEOUT:
(dc + 1) * IDLE_TIMEOUT);
deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
}
else
parkTime = deadline = 0L;
if (w.eventCount == ec && ctl == c) {
Thread wt = Thread.currentThread();
U.putObject(wt, PARKBLOCKER, this);
w.parker = wt; // emulate LockSupport.park
if (w.eventCount == ec && ctl == c)
U.park(false, parkTime); // must recheck before park
w.parker = null;
U.putObject(wt, PARKBLOCKER, null);
if (parkTime != 0L && ctl == c &&
deadline - System.nanoTime() <= 0L &&
U.compareAndSwapLong(this, CTL, c, pc))
stat = w.qlock = -1; // shrink pool
}
}
}
return stat;
}
这个方法用于工作线程发现所有队列为空的情况,进入这个方法的线程要么由于执行器已经关闭,要么之前已经把自己的状态变为等待并且至少扫描4遍所有队列。工作方式如下:
- 如果qlock<0||当前线程的eventCount发生了改变||ctl发生了改变||当前线程处于中断状态(隐含地消除中断),直接返回stat。
- 如果执行器已经被关闭||(活动线程数为0&&当前执行器被关闭),设置当前线程的qlock和stat为-1(结束),返回stat值。
- 如果当前线程的nsteals数不为0(从其他线程队列中偷窃的任务数不为0),把nsteals值为0并且添加进执行者的总计偷窃数(stealCount),返回stat,从而重试。
- 计算pc值:如果当前活动线程数>0或者当前线程下标不为Treiber栈中的顶元素,值为0L;否则值通过当前线程中nextWait域重新计算CTL的得出的值。假如pc不为0L,计算parallelism-TC(线程总数)的值,若为负数parkTime值为FAST_IDLE_TIMEOUT,否则值为((dc+1)*IDLE_TIMEOUT),deadline值为当前时间+parkTime-TIMEOUT_SLOP。假如pc为0,parkTime和deadline都为0。当线程依旧为等待状态以及当前ctl稳定时,park当前线程(使用parkTime,0L为不限时否则限时,这个方法可能无原因的返回),被唤醒之后,假如之前是限时等待&&等待时间已经达到&&CTL稳定&&CAS改变CTL成功,修改stat和当前qlock的值为-1,返回stat。
final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) {
WorkQueue w = null;
if (wt != null && (w = wt.workQueue) != null) {
int ps;
w.qlock = -1; // ensure set
U.getAndAddLong(this, STEALCOUNT, w.nsteals); // collect steals
if (((ps = plock) & PL_LOCK) != 0 ||
!U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
ps = acquirePlock();
int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN);
try {
int idx = w.poolIndex;
WorkQueue[] ws = workQueues;
if (ws != null && idx >= 0 && idx < ws.length && ws[idx] == w)
ws[idx] = null;
} finally {
if (!U.compareAndSwapInt(this, PLOCK, ps, nps))
releasePlock(nps);
}
}
long c; // adjust ctl counts
do {} while (!U.compareAndSwapLong
(this, CTL, c = ctl, (((c - AC_UNIT) & AC_MASK) |
((c - TC_UNIT) & TC_MASK) |
(c & ~(AC_MASK|TC_MASK)))));
if (!tryTerminate(false, false) && w != null && w.array != null) {
w.cancelAll(); // cancel remaining tasks
WorkQueue[] ws; WorkQueue v; Thread p; int u, i, e;
while ((u = (int)((c = ctl) >>> 32)) < 0 && (e = (int)c) >= 0) {
if (e > 0) { // activate or create replacement
if ((ws = workQueues) == null ||
(i = e & SMASK) >= ws.length ||
(v = ws[i]) == null)
break;
long nc = (((long)(v.nextWait & E_MASK)) |
((long)(u + UAC_UNIT) << 32));
if (v.eventCount != (e | INT_SIGN))
break;
if (U.compareAndSwapLong(this, CTL, c, nc)) {
v.eventCount = (e + E_SEQ) & E_MASK;
if ((p = v.parker) != null)
U.unpark(p);
break;
}
}
else {
if ((short)u < 0)
tryAddWorker();
break;
}
}
}
if (ex == null) // help clean refs on way out
ForkJoinTask.helpExpungeStaleExceptions();
else // rethrow
ForkJoinTask.rethrow(ex);
}
这个方法在工作线程中处于“善后”的处理,当线程必须结束自己时,他首先把自己的qlock设置为-1(考虑异常结束的情况),从workQueues中撤销自己的idx(从而为将来新增的线程流出空间),然后递减AC和TC的值,tryTerminate这里用于判断执行器是否关闭,当执行器未关闭&&当前线程的伴随队列不为空时,取消当前线程任务队列中的所有任务(事实上此队列为空)以及currentJoin和currentSteal,当活动线程数(AC)不足&&执行器未关闭时:假如存在等待线程,那么试着唤醒一个等待线程,break。否则如果线程总数同样不足,添加一个新的工作线程,break。最后:
- 如果未抛出异常,那么试着将那些只存在弱引用已经被QUEUE回收的异常节点从表中清除。
- 否则,尝试强制转化异常为RuntimeException从而抛出。
- 从这里可以看出,必定有一个任务的异常处理机制,对的,存在于ForkJoinTask的doExec中。
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;
}
事实上ForkJoinTask是一个抽象类,它真正需要实现的是exec方法,当然getRawResult和setRawResult可以提供无用的实现。
public <T> ForkJoinTask<T> submit(Callable<T> task) {
ForkJoinTask<T> job = new ForkJoinTask.AdaptedCallable<T>(task);
externalPush(job);
return job;
}
我们接下来看得到job之后可以做什么,首先看join方法:
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
这里调用了doJoin方法(事实上返回的值即为status)和DONE_MASK进行对比:
- 假如值为NORMAL,则通过getRawResult返回计算结果(getRawResult属于ForkJoinTask的抽象方法,和exec、setRawResult一起协作)。
- 否则,返回通过reportExecption根据s的值抛出异常。
/** The run status of this task */
volatile int status; // accessed directly by pool and workers
static final int DONE_MASK = 0xf0000000; // mask out non-completion bits
static final int NORMAL = 0xf0000000; // must be negative
static final int CANCELLED = 0xc0000000; // must be < NORMAL
static final int EXCEPTIONAL = 0x80000000; // must be < CANCELLED
static final int SIGNAL = 0x00010000; // must be >= 1 << 16
static final int SMASK = 0x0000ffff; // short bits for tags
有三种状态:NORMAL(任务顺利完成)、CANCELLED(任务被取消)、EXCEPTIONAL(任务报异常),这三种状态均为负,可以方便的探测任务是否已经终结。SIGNAL用于指示处在等待线程,DONE_MASK与SMASK作为MASK使用。status的改变通过CAS操作。
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) :
externalAwaitDone();
}
这个方法的设计及其精巧,它应对了当前线程为工作线程和普通线程两种情况:
- 首先,假如status<0,那么任务已经总结,直接返回status。
- 假如当前线程不为工作线程(ForkJoinWorkerThread),那么调用externalAwaitDone方法等待任务结束并返回任务状态status。
- 假如当前线程为工作线程,那么尝试从当前线程的私有队列的top中移除这个任务,成功的话就直接执行(doExec),然后返回结果状态status。
- 假如失败,那么就调用awaitJoin方法来进一步尝试各种努力。
private int externalAwaitDone() {
int s;
ForkJoinPool cp = ForkJoinPool.common;
if ((s = status) >= 0) {
if (cp != null) {
if (this instanceof CountedCompleter)
s = cp.externalHelpComplete((CountedCompleter<?>)this, Integer.MAX_VALUE);
else if (cp.tryExternalUnpush(this))
s = doExec();
}
if (s >= 0 && (s = status) >= 0) {
boolean interrupted = false;
do {
if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
synchronized (this) {
if (status >= 0) {
try {
wait();
} catch (InterruptedException ie) {
interrupted = true;
}
}
else
notifyAll();
}
}
} while ((s = status) >= 0);
if (interrupted)
Thread.currentThread().interrupt();
}
}
return s;
}
这个方法工作凡是如下:
- 假如当前任务已经终结(status<0),那么直接返回状态。
- 假如公共的执行器对象不为空:假如当前任务属于CountedCompleter,那么使用另一种尝试机制externalHelpComplete(这一部分我们在后面说明);否则如果成功从该执行器中某个探测队列中移除当前任务,那么就执行它,并返回status。
- 再次判断status的值,还未终结时,尝试用SIGNAL修改status的值,从而不会丢失信号的阻塞等待,这个过程中保留现场的中断状态。
final int awaitJoin(WorkQueue joiner, ForkJoinTask<?> task) {
int s = 0;
if (task != null && (s = task.status) >= 0 && joiner != null) {
ForkJoinTask<?> prevJoin = joiner.currentJoin;
joiner.currentJoin = task;
do {} while (joiner.tryRemoveAndExec(task) && // process local tasks
(s = task.status) >= 0);
if (s >= 0 && (task instanceof CountedCompleter))
s = helpComplete(joiner, (CountedCompleter<?>)task, Integer.MAX_VALUE);
long cc = 0; // for stability checks
while (s >= 0 && (s = task.status) >= 0) {
if ((s = tryHelpStealer(joiner, task)) == 0 &&
(s = task.status) >= 0) {
if (!tryCompensate(cc))
cc = ctl;
else {
if (task.trySetSignal() && (s = task.status) >= 0) {
synchronized (task) {
if (task.status >= 0) {
try { // see ForkJoinTask
task.wait(); // for explanation
} catch (InterruptedException ie) {
}
}
else
task.notifyAll();
}
}
long c; // reactivate
do {} while (!U.compareAndSwapLong
(this, CTL, c = ctl,
((c & ~AC_MASK) |
((c & AC_MASK) + AC_UNIT))));
}
}
}
joiner.currentJoin = prevJoin;
}
return s;
}
首先会传入当前工作线程的伴随队列(joiner),以及当前join的任务task。
- 由于对当前线程来说,可能之前已经存在一个join任务,所以我们首先保存currentJoin用prevJoin。
- 第一步,尝试从当前队列中移除task并执行,返回为true并且任务状态未终结时重试;这里与之前不同的是会从顶到尾扫描一遍,并且确定队列中无task并且队列稳定时才返回false退出。
- 第二步,假如当前task任务为CountedCompleter,再次尝试另一种协作方式。
- 第三步,通过tryHelpStealer尝试从偷走当前task的线程队列中偷窃任务,并且可能的话沿着currentJoin->currentSteal链来继续偷窃任务。
- 第四步,通过tryCompensate来为当前线程阻塞之前维护合理的活动工作线程数量,并且在需要的时候会存在超过parallelism的工作线程总数。
- 第五步,设置task任务的SIGNAL信号,阻塞。当阻塞被唤醒之后重新递增CTL中的活动线程数。
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;
}
- 假如执行过程中抛出异常:在我们的exceptionTable(类型为ExceptionNode[])中添加一个异常节点(这里的ExceptionNode继承WeakReference类型),并设置异常状态,返回异常状态。
- 否则,根据返回的completed值:为true的话设置任务状态为完成,否则什么都不做。返回任务状态。
private int setExceptionalCompletion(Throwable ex) {
int s = recordExceptionalCompletion(ex);
if ((s & DONE_MASK) == EXCEPTIONAL)
internalPropagateException(ex);
return s;
}
final int recordExceptionalCompletion(Throwable ex) {
int s;
if ((s = status) >= 0) {
int h = System.identityHashCode(this);
final ReentrantLock lock = exceptionTableLock;
lock.lock();
try {
expungeStaleExceptions();
ExceptionNode[] t = exceptionTable;
int i = h & (t.length - 1);
for (ExceptionNode e = t[i]; ; e = e.next) {
if (e == null) {
t[i] = new ExceptionNode(this, ex, t[i]);
break;
}
if (e.get() == this) // already present
break;
}
} finally {
lock.unlock();
}
s = setCompletion(EXCEPTIONAL);
}
return s;
}
setExceptionalCompletion方法中调用recordExceptionalCompletion方法,并根据状态是否为异常来调用操作internalPropagateException(默认为空)。
private int setCompletion(int completion) {
for (int s;;) {
if ((s = status) < 0)
return s;
if (U.compareAndSwapInt(this, STATUS, s, s | completion)) {
if ((s >>> 16) != 0)
synchronized (this) { notifyAll(); }
return completion;
}
}
}
setCompletion中及其简单,重试着使用CAS操作以及位操作符|来设置状态,假如status<0则直接返回。并且在成功之后查看SIGNAL位是否已经被设置,从而唤醒之前挂起的线程。
public boolean cancel(boolean mayInterruptIfRunning) {
return (setCompletion(CANCELLED) & DONE_MASK) == CANCELLED;
}
只不过把参数换成了CANCELLED。
public void shutdown() {
checkPermission();
tryTerminate(false, true);
}
private boolean tryTerminate(boolean now, boolean enable) {
int ps;
if (this == common) // cannot shut down
return false;
if ((ps = plock) >= 0) { // enable by setting plock
if (!enable)
return false;
if ((ps & PL_LOCK) != 0 ||
!U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
ps = acquirePlock();
int nps = ((ps + PL_LOCK) & ~SHUTDOWN) | SHUTDOWN;
if (!U.compareAndSwapInt(this, PLOCK, ps, nps))
releasePlock(nps);
}
for (long c;;) {
if (((c = ctl) & STOP_BIT) != 0) { // already terminating
if ((short)(c >>> TC_SHIFT) + parallelism <= 0) {
synchronized (this) {
notifyAll(); // signal when 0 workers
}
}
return true;
}
if (!now) { // check if idle & no tasks
WorkQueue[] ws; WorkQueue w;
if ((int)(c >> AC_SHIFT) + parallelism > 0)
return false;
if ((ws = workQueues) != null) {
for (int i = 0; i < ws.length; ++i) {
if ((w = ws[i]) != null &&
(!w.isEmpty() ||
((i & 1) != 0 && w.eventCount >= 0))) {
signalWork(ws, w);
return false;
}
}
}
}
if (U.compareAndSwapLong(this, CTL, c, c | STOP_BIT)) {
for (int pass = 0; pass < 3; ++pass) {
WorkQueue[] ws; WorkQueue w; Thread wt;
if ((ws = workQueues) != null) {
int n = ws.length;
for (int i = 0; i < n; ++i) {
if ((w = ws[i]) != null) {
w.qlock = -1;
if (pass > 0) {
w.cancelAll();
if (pass > 1 && (wt = w.owner) != null) {
if (!wt.isInterrupted()) {
try {
wt.interrupt();
} catch (Throwable ignore) {
}
}
U.unpark(wt);
}
}
}
}
// Wake up workers parked on event queue
int i, e; long cc; Thread p;
while ((e = (int)(cc = ctl) & E_MASK) != 0 &&
(i = e & SMASK) < n && i >= 0 &&
(w = ws[i]) != null) {
long nc = ((long)(w.nextWait & E_MASK) |
((cc + AC_UNIT) & AC_MASK) |
(cc & (TC_MASK|STOP_BIT)));
if (w.eventCount == (e | INT_SIGN) &&
U.compareAndSwapLong(this, CTL, cc, nc)) {
w.eventCount = (e + E_SEQ) & E_MASK;
w.qlock = -1;
if ((p = w.parker) != null)
U.unpark(p);
}
}
}
}
}
}
}
注意有两个参数:now和enable。我们来看它的工作流程:
不理解的地方:
- WorkQueue中的array(ForkJoinTask<?>[]),是如何实现线程间共享的,尽管对array中的元素使用了CAS以及putOrderedInt/getObjectVolatile.
- ForkJoinPool与ThreadPoolExecutor不同,尽管ForkJoinPool相对ThreadPoolExecutor提供了更好的并发执行机制以及更少的阻塞时间,但是ForkJoinPool中的shutdown()调用之后,在理论上存在”任务队列中留有任务,但是工作线程都已结束,残留的任务再也不被执行“这一场景。
- 与CountedCompleter(可在任务层次间建立联系的任务)、ForkJoinPool.ManagedBlocker(一个能和ForkJoin框架建立联系的节点)。