關於框架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框架建立聯繫的節點)。