Selfishness is not living as one wishes to live, it is asking others to live as one wishes to live. -- 王爾德
Just For M
導(dǎo)讀
@since 1.7
Doug Lea為我們帶來了新的并發(fā)框架 - A Java Fork/Join Framework
,在Lea老爺子發(fā)表的一篇papers中為我們描述了其設(shè)計(jì)理念,這個(gè)框架使用的場景就是將一個(gè)大任務(wù)按照意愿切分成N個(gè)小任務(wù)并行執(zhí)行,并最終聚合結(jié)果,加快運(yùn)算。比如我們想要計(jì)算1到1000的sum,就可以使用該框架實(shí)現(xiàn)一個(gè)二分算法的求和。如果你正在使用jdk8為我們帶來的流式計(jì)算api的話,你可能也正在接觸到該框架,因?yàn)榱鞯膒arallel方法底層就是使用ForkJoinPool來處理。
ForkJoin框架到了jdk1.8之后進(jìn)一步做了優(yōu)化,和jdk1.7的實(shí)現(xiàn)方法有很多不同,本文目的就是為了講解jdk1.8的工程實(shí)現(xiàn)。
想要了解核心的設(shè)計(jì)思路,可以提前閱讀Doug Lea的論文:
《A Java Fork/Join Framework》
Java 并發(fā)編程筆記:如何使用 ForkJoinPool 以及原理
概述
和傳統(tǒng)的線程池使用AQS的實(shí)現(xiàn)邏輯不同,ForkJoin
引入全新的結(jié)構(gòu)來標(biāo)識:
- ForkJoinPool: 用于執(zhí)行
ForkJoinTask
任務(wù)的執(zhí)行池,不再是傳統(tǒng)執(zhí)行池 Worker+Queue 的組合模式,而是維護(hù)了一個(gè)隊(duì)列數(shù)組WorkQueue
,這樣在提交任務(wù)和線程任務(wù)的時(shí)候大幅度的減少碰撞。 - WorkQueue: 雙向列表,用于任務(wù)的有序執(zhí)行,如果
WorkQueue
用于自己的執(zhí)行線程Thread
,線程默認(rèn)將會從top端選取任務(wù)用來執(zhí)行 - LIFO。因?yàn)橹挥衞wner的Thread才能從top端取任務(wù),所以在設(shè)置變量時(shí),int top;
不需要使用volatile
。 - ForkJoinWorkThread: 用于執(zhí)行任務(wù)的線程,用于區(qū)別使用非ForkJoinWorkThread線程提交的task;啟動一個(gè)該Thread,會自動注冊一個(gè)WorkQueue到Pool,這里規(guī)定,擁有Thread的WorkQueue只能出現(xiàn)在WorkQueue數(shù)組的奇數(shù)位
- ForkJoinTask: 任務(wù), 它比傳統(tǒng)的任務(wù)更加輕量,不再對是RUNNABLE的子類,提供
fork
/join
方法用于分割任務(wù)以及聚合結(jié)果。 - 為了充分施展并行運(yùn)算,該框架實(shí)現(xiàn)了復(fù)雜的 worker steal算法,當(dāng)任務(wù)處于等待中,thread通過一定策略,不讓自己掛起,充分利用資源,當(dāng)然,它比其他語言的協(xié)程要重一些。
ForkJoinPool變量基本說明
作為框架的提交入口,ForkJoinPool
管理著線程池中線程和任務(wù)隊(duì)列,標(biāo)識線程池是否還接收任務(wù),顯示現(xiàn)在的線程運(yùn)行狀態(tài)。本節(jié),對這些控制量進(jìn)行解釋。
如果讀者看過 類似 disrupter
這種高效率隊(duì)列的開源實(shí)現(xiàn),大家肯定會對cache line記憶猶新,他們通常的做法自己設(shè)置偽變量來填充,jdk1.8?中官網(wǎng)為我們帶來了sun.misc.Contended,所以你如果閱讀ForkJoinPool
源碼可以發(fā)現(xiàn)該類也被sun.misc.Contended
標(biāo)識。
幾個(gè)重要變量:
- runState: 標(biāo)識
Pool
運(yùn)行狀態(tài),使用bit位來標(biāo)識不同狀態(tài),比如
如果執(zhí)行// runState bits: SHUTDOWN must be negative, others arbitrary powers of two 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;
runState & RSLOCK ==0
就能直接說明,目前的運(yùn)行狀態(tài)沒有被鎖住,其他情況一樣。 - config:parallelism | mode
- parallelism: 這個(gè)變量不是內(nèi)部定義的變量,但是需要各位注意一下它的界限,因?yàn)楹竺娴奶幚硇枰⒁?
static final int MAX_CAP = 0x7fff; // max #workers - 1
也就是說他最大就占16位
- ctl:ctl是
Pool
的控制變量,類型是long - 說明有64位,每個(gè)部分都有不同的作用。我們使用十六進(jìn)制來標(biāo)識ctl,依次說明不同部分的作用。
我為每個(gè)部分使用了數(shù)字來標(biāo)識 - 1,2,3,4。long np = (long)(-parallelism); // offset ctl counts this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK); 0x xxxx-1 xxxx-2 xxxx-3 xxxx-4
- 編號為1的16位: AC 表示現(xiàn)在獲取的線程數(shù),這里的初始化比較有技巧,使用的是并行數(shù)的相反數(shù),這樣如果active的線程數(shù),還沒到達(dá)了我們設(shè)置的閾值的時(shí)候,ctl是個(gè)負(fù)數(shù),我們可以根據(jù)ctl的正負(fù)直觀的知道現(xiàn)在的并行數(shù)達(dá)到閾值了么。
- 編號為2的16位:TC 表示線程總量,初始值也是并行數(shù)的相反數(shù)。這里需要說明一下,這個(gè)編號1所表示的活躍的線程數(shù)的區(qū)別,我們雖然開啟了并行數(shù)等量的線程,但是可能在某些條件下,運(yùn)行的thread不得不
wait
或者park
,原因我們后面會提到,這個(gè)時(shí)候,雖然我們開啟的線程數(shù)量是和并行數(shù)相同,但是實(shí)際真正執(zhí)行的卻不是這么多。TC 記錄了我們一共開啟了多少線程,而AC則記錄了沒有掛起的線程。 - 編號為3的16位:后32位標(biāo)識 idle workers 前面16位第一位標(biāo)識是
active
的還是inactive
的,其他為是版本標(biāo)識。 - 編號為4的16位:標(biāo)識idle workers 在
WorkQueue[]
數(shù)組中的index。這里需要說明的是,ctl的后32位其實(shí)只能表示一個(gè)idle workers,那么我們?nèi)绻泻芏鄠€(gè)idle worker要怎么辦呢?老爺子使用的是stack
的概念來保存這些信息。后32位標(biāo)識的是top的那個(gè),我們能從top中的變量stackPred
追蹤到下一個(gè)idle worker
WorkQueue變量基本說明
WorkQueue
是一個(gè)雙向列表,用于task的排隊(duì)。
幾個(gè)變量的定義說明:
-
scanState:
// versioned, <0: inactive; odd:scanning
如果WorkQueue
沒有屬于自己的owner
(下標(biāo)為偶數(shù)的都沒有),該值為 inactive 也就是一個(gè)負(fù)數(shù)。如果有自己的owner
,該值的初始值為其在WorkQueue[]
數(shù)組中的下標(biāo),也肯定是個(gè)奇數(shù)。
如果這個(gè)值,變成了偶數(shù),說明該隊(duì)列所屬的Thread正在執(zhí)行Taskstatic final int SCANNING = 1; // false when running tasks static final int INACTIVE = 1 << 31; // must be negative
stackPred: 記錄前任的
idle worker
-
config:index | mode。 如果下標(biāo)為偶數(shù)的
WorkQueue
,則其mode是共享類型。如果有自己的owner
默認(rèn)是 LIFO什么時(shí)候應(yīng)該設(shè)置成 FIFO,注釋中這么給的建議:
establishes local first-in-first-out scheduling mode for forked
tasks that are never joined. This mode may be more appropriate
than default locally stack-based mode in applications in which
worker threads only process event-style asynchronous tasks.
For default value, use {@code false} qlock: 鎖標(biāo)識,在多線程往隊(duì)列中添加數(shù)據(jù),會有競爭,使用此標(biāo)識搶占鎖。
base:worker steal的偏移量,因?yàn)槠渌木€程都可以偷該隊(duì)列的任務(wù),所有base使用
volatile
標(biāo)識。top:
owner
執(zhí)行任務(wù)的偏移量。parker:如果
owner
掛起,則使用該變量做記錄。currentJoin: 當(dāng)前正在join等待結(jié)果的任務(wù)。
currentSteal:當(dāng)前執(zhí)行的任務(wù)是steal過來的任務(wù),該變量做記錄。
ForkJoinTask變量基本說明
- status: 標(biāo)識任務(wù)目前的狀態(tài),如果<0,表示任務(wù)處于結(jié)束狀態(tài)。
((s >>> 16) != 0)
表示需要signal
其他線程
任務(wù)提交過程剖析
ForkJoinPool
提供的提交接口很多,不管提交的是Callable
、Runnable
、ForkJoinTask
最終都會轉(zhuǎn)換成ForkJoinTask
類型的任務(wù),調(diào)用方法externalPush(ForkJoinTask<?> task)
來進(jìn)行提交邏輯。讓我們來看看提交的過程:
-
如果第一次提交(或者是hash之后的隊(duì)列還未初始化),調(diào)用
externalSubmit
- 第一遍循環(huán): (runState不是開始狀態(tài)): 1.lock; 2.創(chuàng)建數(shù)組
WorkQueue[n]
,這里的n是power of 2; 3. runState設(shè)置為開始狀態(tài)。 - 第二遍循環(huán):(根據(jù)
ThreadLocalRandom.getProbe()
hash后的數(shù)組中相應(yīng)位置的WorkQueue
未初始化): 初始化WorkQueue
,通過這種方式創(chuàng)立的WorkQueue
均是SHARED_QUEUE
,scanState
為INACTIVE
- 第三遍循環(huán): 找到剛剛創(chuàng)建的
WorkQueue
,lock住隊(duì)列,將數(shù)據(jù)塞到array
top位置。如果添加成功,就用調(diào)用接下來要攤開講的重要的方法signalWork
。
- 第一遍循環(huán): (runState不是開始狀態(tài)): 1.lock; 2.創(chuàng)建數(shù)組
-
如果hash之后的隊(duì)列已經(jīng)存在
- lock住隊(duì)列,將數(shù)據(jù)塞到top位置。如果該隊(duì)列任務(wù)很少(n <= 1)也會調(diào)用
signalWork
- lock住隊(duì)列,將數(shù)據(jù)塞到top位置。如果該隊(duì)列任務(wù)很少(n <= 1)也會調(diào)用
signalWork
signalWork
是fork/join框架中重要的方法之一,用于創(chuàng)建或者激活工作線程。本小節(jié)主要看它的源碼實(shí)現(xiàn),文章后面我們會總結(jié)它的使用場景。
final void signalWork(WorkQueue[] ws, WorkQueue q) {
long c; int sp, i; WorkQueue v; Thread p;
while ((c = ctl) < 0L) { // too few active
if ((sp = (int)c) == 0) { // no idle workers
if ((c & ADD_WORKER) != 0L) // too few workers
tryAddWorker(c);
break;
}
if (ws == null) // unstarted/terminated
break;
if (ws.length <= (i = sp & SMASK)) // terminated
break;
if ((v = ws[i]) == null) // terminating
break;
int vs = (sp + SS_SEQ) & ~INACTIVE; // next scanState
int d = sp - v.scanState; // screen CAS
long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
v.scanState = vs; // activate v
if ((p = v.parker) != null)
U.unpark(p);
break;
}
if (q != null && q.base == q.top) // no more work
break;
}
}
在上面的章節(jié)我們已經(jīng)具體的分析了ForkJoinPool
中的ctl的標(biāo)識含義,我們知道當(dāng)ctl<0
意味著active的線程還沒有到達(dá)閾值,只有ctl<0
我們才會去討論要不要創(chuàng)建或者激活新的線程。(int)ctl
很巧妙的拿到了ctl
的低16位
-
我們知道
ctl
代表的是idle worker當(dāng)?shù)?6位為0的時(shí)候,意味著此刻沒有已經(jīng)啟動但是空閑的線程,如果在沒有空閑的線程的情況下(c & ADD_WORKER) != 0L private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15); // sign
意味著我們再增加一個(gè)線程,也不能使ac為非負(fù)數(shù)(只要ctl的最高位為1,表明ac仍是負(fù)數(shù)) 。我們調(diào)用方法
tryAddWorker
來創(chuàng)建工作線程。- 我們首先需要使用
ctl
來記錄我們增加的線程,ctl
編號-1的16位和編號-2的16位均需要加1,表示active的worker加一,總的worker加一。成功后我們將調(diào)用createWorker
。 - 我們使用
ForkJoinWorkerThreadFactory
來產(chǎn)生一個(gè)ForkJoinWorkerThread
類型的線程,該線程將會把自己注冊到Pool上,怎么注冊的呢?實(shí)現(xiàn)在方法registerWorker
,前文我們已經(jīng)提及,擁有線程的WorkQueue
只能出現(xiàn)在數(shù)組的奇數(shù)下標(biāo)處。所以線程 首先,創(chuàng)建一個(gè)新的WorkQueue
,其次在數(shù)組WorkQueue[]
尋找奇數(shù)下標(biāo)尚未初始化的位置,如果循環(huán)的次數(shù)大于數(shù)組長度,還可能需要對數(shù)組進(jìn)行擴(kuò)容,然后,設(shè)置這個(gè)WorkQueue
的 config 為 index | mode (下標(biāo)和模式),scanState為 index (下標(biāo)>0)。最后啟動這個(gè)線程。線程的處理我們接下來的章節(jié)介紹。
- 我們首先需要使用
-
當(dāng)我們發(fā)現(xiàn)我們還有
idle worker
(即(int)ctl!=0L
),我們需要active其中的一個(gè)。- 我們上文說過
ctl
的編號-3的16位,標(biāo)記inactive和版本控制,我們將編號-3設(shè)置為激活狀態(tài)并且版本加一。編號-4的16位我們之前也說過,放置了top的掛起的線程的index
所以我們可以根據(jù)這個(gè)index
拿到WorkQueue
--- 意味著就是這個(gè)WorkQueue
的Owner
線程被掛起了。 - 我們將要把top的掛起線程喚醒,意味著我們要講下一個(gè)掛起的線程的信息記錄到ctl上。前文也說在上一個(gè)掛起的線程的
index
信息在這個(gè)掛起的線程的stackPred
更新完int vs = (sp + SS_SEQ) & ~INACTIVE; // next scanState int d = sp - v.scanState; // screen CAS long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred); if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) { v.scanState = vs; // activate v if ((p = v.parker) != null) U.unpark(p); break; }
ctl
后,我們將喚醒之前掛起的線程。
通過上述的簡單介紹,用戶的任務(wù)的提交所經(jīng)歷的步驟就介紹完了。
- 我們上文說過
ForkJoinWorkerThread運(yùn)行過程剖析
ForkJoinWorkerThread
啟動之后會調(diào)用pool
的runWorker
來獲取任務(wù)執(zhí)行。
final void runWorker(WorkQueue w) {
w.growArray(); // allocate queue
int seed = w.hint; // initially holds randomization hint
int r = (seed == 0) ? 1 : seed; // avoid 0 for xorShift
for (ForkJoinTask<?> t;;) {
if ((t = scan(w, r)) != null)
w.runTask(t);
else if (!awaitWork(w, r))
break;
r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
}
}
代碼很短,但是我們很容易讀懂其中的意思。調(diào)用scan
嘗試去偷取一個(gè)任務(wù),然后調(diào)用runTask
或者awaitWork
,這里的scan
是框架的重要的實(shí)現(xiàn),我們將詳述上面的三個(gè)方法。
scan
代碼如下:
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) {
if ((n = (b = q.base) - q.top) < 0 &&
(a = q.array) != null) { // non-empty
long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
if ((t = ((ForkJoinTask<?>)
U.getObjectVolatile(a, i))) != null &&
q.base == b) {
if (ss >= 0) {
if (U.compareAndSwapObject(a, i, t, null)) {
q.base = b + 1;
if (n < -1) // signal others
signalWork(ws, q);
return t;
}
}
else if (oldSum == 0 && // try to activate
w.scanState < 0)
tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
}
if (ss < 0) // refresh
ss = w.scanState;
r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
origin = k = r & m; // move and rescan
oldSum = checkSum = 0;
continue;
}
checkSum += b;
}
if ((k = (k + 1) & m) == origin) { // continue until stable
if ((ss >= 0 || (ss == (ss = w.scanState))) &&
oldSum == (oldSum = checkSum)) {
if (ss < 0 || w.qlock < 0) // already inactive
break;
int ns = ss | INACTIVE; // try to inactivate
long nc = ((SP_MASK & ns) |
(UC_MASK & ((c = ctl) - AC_UNIT)));
w.stackPred = (int)c; // hold prev stack top
U.putInt(w, QSCANSTATE, ns);
if (U.compareAndSwapLong(this, CTL, c, nc))
ss = ns;
else
w.scanState = ss; // back out
}
checkSum = 0;
}
}
}
return null;
}
因?yàn)槲覀兊?code>WorkQueue是有owner
線程的隊(duì)列,我們可以知道以下信息:
- config = index | mode
- scanState = index > 0
我們首先通過random的r
來找到一個(gè)我們準(zhǔn)備偷取的隊(duì)列。
- 如果我們準(zhǔn)備偷取的隊(duì)列剛好有任務(wù)在排隊(duì)(也有可能是owner自己的那個(gè)隊(duì)列);
- 從隊(duì)列的隊(duì)尾即
base
位置取到任務(wù)返回 - base + 1
- 從隊(duì)列的隊(duì)尾即
- 如果我們遍歷了一圈(
((k = (k + 1) & m) == origin)
)都沒有偷到,我們就認(rèn)為當(dāng)前的active 線程過剩了,我們準(zhǔn)備將當(dāng)前的線程(即owner
)掛起,我們首先index | INACTIVE
形成ctl
的后32位;并行將ac減一。其次,將原來的掛起的top的index
記錄到stackPred
中。 - 繼續(xù)遍歷如果仍然一無所獲,將跳出循環(huán);如果偷到了一個(gè)任務(wù),我們將使用
tryRelease
激活。
runTask
如果我們通過scan
偷到了任務(wù),這個(gè)時(shí)候我們將進(jìn)入執(zhí)行狀態(tài):
final void runTask(ForkJoinTask<?> task) {
if (task != null) {
scanState &= ~SCANNING; // mark as busy
(currentSteal = task).doExec();
U.putOrderedObject(this, QCURRENTSTEAL, null); // release for GC
execLocalTasks();
ForkJoinWorkerThread thread = owner;
if (++nsteals < 0) // collect on overflow
transferStealCount(pool);
scanState |= SCANNING;
if (thread != null)
thread.afterTopLevelExec();
}
}
首先scanState &= ~SCANNING;
標(biāo)識該線程處于繁忙狀態(tài)。
- 執(zhí)行偷取的Task。
- 調(diào)用
execLocalTasks
對線程所屬的WorkQueue
內(nèi)的任務(wù)進(jìn)行LIFO執(zhí)行。
awaitWork
如果我們通過scan
一無所獲,這個(gè)時(shí)候我們將進(jìn)入執(zhí)行狀態(tài):
private boolean awaitWork(WorkQueue w, int r) {
if (w == null || w.qlock < 0) // w is terminating
return false;
for (int pred = w.stackPred, spins = SPINS, ss;;) {
if ((ss = w.scanState) >= 0)
break;
else if (spins > 0) {
r ^= r << 6; r ^= r >>> 21; r ^= r << 7;
if (r >= 0 && --spins == 0) { // randomize spins
WorkQueue v; WorkQueue[] ws; int s, j; AtomicLong sc;
if (pred != 0 && (ws = workQueues) != null &&
(j = pred & SMASK) < ws.length &&
(v = ws[j]) != null && // see if pred parking
(v.parker == null || v.scanState >= 0))
spins = SPINS; // continue spinning
}
}
else if (w.qlock < 0) // recheck after spins
return false;
else if (!Thread.interrupted()) {
long c, prevctl, parkTime, deadline;
int ac = (int)((c = ctl) >> AC_SHIFT) + (config & SMASK);
if ((ac <= 0 && tryTerminate(false, false)) ||
(runState & STOP) != 0) // pool terminating
return false;
if (ac <= 0 && ss == (int)c) { // is last waiter
prevctl = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & pred);
int t = (short)(c >>> TC_SHIFT); // shrink excess spares
if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl))
return false; // else use timed wait
parkTime = IDLE_TIMEOUT * ((t >= 0) ? 1 : 1 - t);
deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
}
else
prevctl = parkTime = deadline = 0L;
Thread wt = Thread.currentThread();
U.putObject(wt, PARKBLOCKER, this); // emulate LockSupport
w.parker = wt;
if (w.scanState < 0 && ctl == c) // recheck before park
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))
return false; // shrink pool
}
}
return true;
}
- 如果ac還沒到達(dá)閾值,但是tc>2 說明現(xiàn)在仍然運(yùn)行中的線程和掛起的線程加一起處于過剩狀態(tài),我們將放棄該線程的掛起,直接讓它執(zhí)行結(jié)束,不再循環(huán)執(zhí)行任務(wù)。
- 否則,我們計(jì)算一個(gè)掛起的時(shí)間,等到了時(shí)間之后(或者被外部喚醒),線程醒了之后,如果發(fā)現(xiàn)自己狀態(tài)是
active
狀態(tài)(w.scanState >= 0
),則線程繼續(xù)回去scan任務(wù),如果發(fā)現(xiàn)自己是因?yàn)闀r(shí)間到了自動醒來,但是自己還是inactive
狀態(tài),也許,自己真的是多余的。線程也會執(zhí)行結(jié)束,不再循環(huán)執(zhí)行任務(wù)。
ForkJoinTask執(zhí)行過程剖析
從上節(jié)我們知道,我們獲取任務(wù)之后,將調(diào)用任務(wù)的doExec
來具體執(zhí)行任務(wù):
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;
}
我們以RecursiveTask
為例:
protected final boolean exec() {
result = compute();
return true;
}
最終調(diào)用的是 compute
,我們舉個(gè)example來看。
demo
求整數(shù)數(shù)組所有元素之和
public class ForkJoinCalculator implements Calculator {
private ForkJoinPool pool;
private static class SumTask extends RecursiveTask<Long> {
private long[] numbers;
private int from;
private int to;
public SumTask(long[] numbers, int from, int to) {
this.numbers = numbers;
this.from = from;
this.to = to;
}
@Override
protected Long compute() {
// 當(dāng)需要計(jì)算的數(shù)字小于6時(shí),直接計(jì)算結(jié)果
if (to - from < 6) {
long total = 0;
for (int i = from; i <= to; i++) {
total += numbers[i];
}
return total;
// 否則,把任務(wù)一分為二,遞歸計(jì)算
} else {
int middle = (from + to) / 2;
SumTask taskLeft = new SumTask(numbers, from, middle);
SumTask taskRight = new SumTask(numbers, middle+1, to);
taskLeft.fork();
taskRight.fork();
return taskLeft.join() + taskRight.join();
}
}
}
public ForkJoinCalculator() {
// 也可以使用公用的 ForkJoinPool:
// pool = ForkJoinPool.commonPool()
pool = new ForkJoinPool();
}
@Override
public long sumUp(long[] numbers) {
return pool.invoke(new SumTask(numbers, 0, numbers.length-1));
}
}
這里需要注意的是其中的compute
- 如果任務(wù)很小,直接計(jì)算結(jié)果。
- 如果任務(wù)很大,一分為二,調(diào)用
fork
方法,獲取join
方法的返回值。
很明顯,這里任務(wù)的 fork
、join
就是我們框架的核心,那么,它們都做了什么呢?
fork
fork的邏輯很簡單。
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;
}
- 如果當(dāng)前線程是工作線程,直接
push
到自己所擁有的隊(duì)列的top
位置。 - 如果是非工作線程,就是一個(gè)提交到Pool的過程。
join
join
是一個(gè)等待結(jié)果的方法:
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
- 如果得到的結(jié)果異常,則拋出異常;
- 如果得到的正常,則獲取返回值。
那么,線程在doJoin
做了什么呢?
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();
}
- 如果當(dāng)前線程不是工作線程,則調(diào)用
externalAwaitDone
,首先,設(shè)置任務(wù)的status為signal狀態(tài),這樣該任務(wù)執(zhí)行結(jié)束之后會調(diào)用notifyAll
來喚醒自己;其次,阻塞自己,知道任務(wù)執(zhí)行完成后把自己喚醒。 - 如果需要join的任務(wù)已經(jīng)完成,直接返回運(yùn)行結(jié)果;
- 如果需要join的任務(wù)剛剛好是當(dāng)前線程所擁有的隊(duì)列的top位置,這意味著當(dāng)前工作線程下一個(gè)就將執(zhí)行到它,則執(zhí)行它。
- 如果該任務(wù)不在top位置,則調(diào)用
awaitJoin
方法:- 設(shè)置
currentJoin
表明自己正在等待該任務(wù); - 如果發(fā)現(xiàn)
w.base == w.top
或者tryRemoveAndExec
返回 true說明自己所屬的隊(duì)列為空,也說明我們通過fork將本線程的任務(wù)已經(jīng)被別的線程偷走,該線程也不會閑著,將會helpStealer
幫助幫助自己執(zhí)行任務(wù)的線程執(zhí)行任務(wù)(互惠互利,你來我往) - 如果
tryCompensate
為 true,則阻塞本線程,等待任務(wù)執(zhí)行結(jié)束的喚醒
- 設(shè)置
tryRemoveAndExec
如果join的任務(wù)還沒有被執(zhí)行,我們?nèi)プ约旱年?duì)列中去查找,看看任務(wù)是否不在top位置但是還是在隊(duì)列中
while ((n = (s = top) - (b = base)) > 0) {
for (ForkJoinTask<?> t;;) { // traverse from s to b
long j = ((--s & m) << ASHIFT) + ABASE;
if ((t = (ForkJoinTask<?>)U.getObject(a, j)) == null)
return s + 1 == top; // shorter than expected
else if (t == task) {
boolean removed = false;
if (s + 1 == top) { // pop
if (U.compareAndSwapObject(a, j, task, null)) {
U.putOrderedInt(this, QTOP, s);
removed = true;
}
}
else if (base == b) // replace with proxy
removed = U.compareAndSwapObject(
a, j, task, new EmptyTask());
if (removed)
task.doExec();
break;
}
else if (t.status < 0 && s + 1 == top) {
if (U.compareAndSwapObject(a, j, t, null))
U.putOrderedInt(this, QTOP, s);
break; // was cancelled
}
if (--n == 0)
return false;
}
if (task.status < 0)
return false;
}
- 如果剛好在top位置,pop出來執(zhí)行。
- 如果在隊(duì)列中間,則使用
EmptyTask
來占位,將任務(wù)取出來執(zhí)行。 - 如果執(zhí)行的任務(wù)還沒結(jié)束。則不進(jìn)行
helpStealer
,
helpStealer
helpStealer
的原則是你幫助我執(zhí)行任務(wù),我也幫你執(zhí)行任務(wù)。
- 遍歷
奇數(shù)
下標(biāo),如果發(fā)現(xiàn)隊(duì)列對象currentSteal
放置的剛好是自己要找的任務(wù),則說明自己的任務(wù)被該隊(duì)列A的owner
線程偷來執(zhí)行 - 如果隊(duì)列A隊(duì)列中有任務(wù),則從隊(duì)尾(
base
)取出執(zhí)行; - 如果發(fā)現(xiàn)隊(duì)列A隊(duì)列為空,則根據(jù)它正在join的任務(wù),在拓?fù)湔业较嚓P(guān)的隊(duì)列B去偷取任務(wù)執(zhí)行。
在執(zhí)行的過程中要注意,我們應(yīng)該完整的把任務(wù)完成
do {
U.putOrderedObject(w, QCURRENTSTEAL, t);
t.doExec(); // clear local tasks too
} while (task.status >= 0 &&
w.top != top &&
(t = w.pop()) != null);
這是什么意思呢? 因?yàn)槟阍趫?zhí)行這個(gè)任務(wù)的時(shí)候,這個(gè)任務(wù)也可能fork出什么子任務(wù)push到當(dāng)前線程,我們應(yīng)該記錄原來隊(duì)列top的位置,然后在執(zhí)行結(jié)束后,還回到top原來的位置。
- 幫忙執(zhí)行任務(wù)完成后,如果發(fā)現(xiàn)自己的隊(duì)列有任務(wù)了(
w.base != w.top
),在不再幫助執(zhí)行任務(wù)了。 - 否則在等待自己的join的那個(gè)任務(wù)結(jié)束之前,可以不斷的偷取任務(wù)執(zhí)行。
tryCompensate
如果自己等待的任務(wù)被偷走執(zhí)行還沒結(jié)束,自己的隊(duì)列還有任務(wù),我們需要做一些補(bǔ)償
private boolean tryCompensate(WorkQueue w) {
boolean canBlock;
WorkQueue[] ws; long c; int m, pc, sp;
if (w == null || w.qlock < 0 || // caller terminating
(ws = workQueues) == null || (m = ws.length - 1) <= 0 ||
(pc = config & SMASK) == 0) // parallelism disabled
canBlock = false;
else if ((sp = (int)(c = ctl)) != 0) // release idle worker
canBlock = tryRelease(c, ws[sp & m], 0L);
else {
int ac = (int)(c >> AC_SHIFT) + pc;
int tc = (short)(c >> TC_SHIFT) + pc;
int nbusy = 0; // validate saturation
for (int i = 0; i <= m; ++i) { // two passes of odd indices
WorkQueue v;
if ((v = ws[((i << 1) | 1) & m]) != null) {
if ((v.scanState & SCANNING) != 0)
break;
++nbusy;
}
}
if (nbusy != (tc << 1) || ctl != c)
canBlock = false; // unstable or stale
else if (tc >= pc && ac > 1 && w.isEmpty()) {
long nc = ((AC_MASK & (c - AC_UNIT)) |
(~AC_MASK & c)); // uncompensated
canBlock = U.compareAndSwapLong(this, CTL, c, nc);
}
else if (tc >= MAX_CAP ||
(this == common && tc >= pc + commonMaxSpares))
throw new RejectedExecutionException(
"Thread limit exceeded replacing blocked worker");
else { // similar to tryAddWorker
boolean add = false; int rs; // CAS within lock
long nc = ((AC_MASK & c) |
(TC_MASK & (c + TC_UNIT)));
if (((rs = lockRunState()) & STOP) == 0)
add = U.compareAndSwapLong(this, CTL, c, nc);
unlockRunState(rs, rs & ~RSLOCK);
canBlock = add && createWorker(); // throws on exception
}
}
return canBlock;
}
- 如果
((sp = (int)(c = ctl)) != 0)
說明還有idle worker
則可以選擇喚醒一個(gè)線程替代自己,掛起自己等待任務(wù)來喚醒自己。 - 如果沒有
idle worker
則額外創(chuàng)建一個(gè)新的工作線程替代自己,掛起自己等待任務(wù)來喚醒自己。
總結(jié)
在了解了 Fork/Join Framework 的工作原理之后,相信很多使用上的注意事項(xiàng)就可以從原理中找到原因