引言
在《(十二)徹悟并發(fā)之JUC分治思想產(chǎn)物-ForkJoin分支合并框架原理剖析上篇》中,我們?cè)醪搅私饬薋orkJoin分支合并框架的使用,也分析框架的成員構(gòu)成以及任務(wù)提交和創(chuàng)建工作的原理實(shí)現(xiàn),在本篇?jiǎng)t會(huì)對(duì)框架的任務(wù)執(zhí)行、任務(wù)掃描、線程掛起、結(jié)果合并以及任務(wù)竊取的源碼實(shí)現(xiàn)進(jìn)行分析。
一、工作線程執(zhí)行任務(wù)/工作竊取實(shí)現(xiàn)過(guò)程
在上篇的最后,從signalWork() -> tryAddWorker() -> createWorker() -> newThread() -> ForkJoinWorkerThread() -> registerWorker() -> deregisterWorker()
這條路線分析完了工作線程的注冊(cè)與銷毀原理實(shí)現(xiàn)。下面接著繼續(xù)來(lái)分析工作線程執(zhí)行任務(wù)的過(guò)程,先回到之前的createWorker()
方法:
// ForkJoinPool類 → createWorker()方法
private boolean createWorker() {
ForkJoinWorkerThreadFactory fac = factory;
Throwable ex = null;
ForkJoinWorkerThread wt = null;
try {
if (fac != null && (wt = fac.newThread(this)) != null) {
// 創(chuàng)建成功則調(diào)用start()方法執(zhí)行
wt.start();
return true;
}
} catch (Throwable rex) {
ex = rex;
}
// 如果創(chuàng)建過(guò)程出現(xiàn)異常則注銷線程
deregisterWorker(wt, ex);
return false;
}
可以很明顯的看到,創(chuàng)建線程成功后則會(huì)開始調(diào)用start()
方法執(zhí)行任務(wù),最終會(huì)找到run()
方法執(zhí)行它:
// ForkJoinWorkerThread類 → run()方法
public void run() {
// 如果任務(wù)數(shù)組不為空
if (workQueue.array == null) {
Throwable exception = null;
try {
// 鉤子函數(shù),用于拓展,這里是空實(shí)現(xiàn)
onStart();
// 使用線程池的runWorker方法執(zhí)行隊(duì)列任務(wù)
pool.runWorker(workQueue);
} catch (Throwable ex) {
// 如果執(zhí)行過(guò)程中出現(xiàn)異常則先記錄
exception = ex;
} finally {
try {
// 鉤子函數(shù),報(bào)告異常
onTermination(exception);
} catch (Throwable ex) {
if (exception == null)
exception = ex;
} finally {
// 如果執(zhí)行出現(xiàn)異常,注銷線程
pool.deregisterWorker(this, exception);
}
}
}
}
// ForkJoinPool類 → runWorker()方法
final void runWorker(WorkQueue w) {
// 初始化任務(wù)數(shù)組,任務(wù)數(shù)組一開始是沒(méi)有初始化的
// 這個(gè)方法是初始化或兩倍擴(kuò)容數(shù)組
w.growArray();
// 獲取注冊(cè)隊(duì)列時(shí)記錄的用于計(jì)算索引的隨機(jī)種子
int seed = w.hint;
// 如果種子為0,那么則改為1,避免使用0
int r = (seed == 0) ? 1 : seed;
// 死循環(huán)
for (ForkJoinTask<?> t;;) {
// 掃描任務(wù):在池的隊(duì)列數(shù)組中隨機(jī)選擇工作隊(duì)列,獲取任務(wù)執(zhí)行
if ((t = scan(w, r)) != null)
// 如果獲取到任務(wù)則執(zhí)行
w.runTask(t);
// 沒(méi)有掃描到任務(wù)則嘗試自旋或掛起阻塞
else if (!awaitWork(w, r))
break;
// 每次執(zhí)行完后修改隨機(jī)值,換個(gè)隊(duì)列獲取任務(wù)
r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
}
}
// ForkJoinPool類 → scan()方法
private ForkJoinTask<?> scan(WorkQueue w, int r) {
WorkQueue[] ws; int m;
// 如果隊(duì)列數(shù)組不為空并且任務(wù)隊(duì)列已經(jīng)初始化且不為空
if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
// 獲取當(dāng)前隊(duì)列scanState,最開始為隊(duì)列在數(shù)組中的下標(biāo)
int ss = w.scanState;
// r&m:隨機(jī)得到一個(gè)數(shù)組中的下標(biāo),oldSum/checkSum:比較效驗(yàn)和的標(biāo)識(shí)
// 開啟循環(huán)
for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
WorkQueue q; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
int b, n; long c;
// 如果隨機(jī)出的下標(biāo)位置隊(duì)列不為空
if ((q = ws[k]) != null) {
// 判斷隊(duì)列中有沒(méi)有任務(wù)
if ((n = (b = q.base) - q.top) < 0 &&
(a = q.array) != null) {
// FIFO模式,通過(guò)內(nèi)存偏移量計(jì)算出棧底/隊(duì)頭位置
long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
// 獲取棧底的任務(wù)
if ((t = ((ForkJoinTask<?>)
U.getObjectVolatile(a, i))) != null &&
q.base == b) {
// 如果工作線程處于活躍狀態(tài)
if (ss >= 0) {
// 嘗試?yán)肅AS機(jī)制搶占線程(可能存在多個(gè)線程)
if (U.compareAndSwapObject(a, i, t, null)) {
// 搶占任務(wù)成功后將棧底挪一個(gè)位置
// 方便其他線程繼續(xù)獲取任務(wù)
q.base = b + 1;
// 如果隊(duì)列中還剩有其他任務(wù)
if (n < -1) // signal others
// 新建或喚醒一條線程繼續(xù)處理
signalWork(ws, q);
return t;
}
}
// 如果當(dāng)前線程未激活,處于阻塞狀態(tài)
else if (oldSum == 0 && // try to activate
w.scanState < 0)
// 喚醒線程
tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
}
// 更新一次scanState值(因?yàn)榍懊婵赡軉拘蚜司€程)
if (ss < 0) // refresh
ss = w.scanState;
// 獲取一個(gè)新的隨機(jī)值,用于隨機(jī)下一個(gè)索引位置
r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
// 根據(jù)新的隨機(jī)種子計(jì)算出一個(gè)新的下標(biāo)索引
origin = k = r & m; // move and rescan
// 效驗(yàn)和的標(biāo)識(shí)復(fù)位
oldSum = checkSum = 0;
// 結(jié)束本次循環(huán),繼續(xù)下次循環(huán)
continue;
}
// 如果沒(méi)有獲取到任務(wù),checkSum+1,表示遍歷完了一個(gè)位置
// 用于效驗(yàn)
checkSum += b;
}
// k=(k+1)&m代表去隊(duì)列數(shù)組的下個(gè)位置繼續(xù)查找下個(gè)坑位的隊(duì)列,
// 如果 ==origin 了,代表已經(jīng)遍歷了所有的隊(duì)列
if ((k = (k + 1) & m) == origin) { // continue until stable
// 如果工作線程還處于活躍狀態(tài)并且掃描完成整個(gè)隊(duì)列后,
// 效驗(yàn)和 還未發(fā)生改變,那代表著沒(méi)有新的任務(wù)提交到線程池
if ((ss >= 0 || (ss == (ss = w.scanState))) &&
oldSum == (oldSum = checkSum)) {
// 如果活躍狀態(tài)變?yōu)榱?lt;0,代表已經(jīng)處于不活躍狀態(tài)
// 那么則退出掃描,返回null,回到runWorker()阻塞線程
if (ss < 0 || w.qlock < 0) // already inactive
break;
// 滅活操作(滅活后的線程被稱為失活狀態(tài)):
// 先將當(dāng)前scanState變?yōu)樨?fù)數(shù)
int ns = ss | INACTIVE; // try to inactivate
// 在ctl中減去一個(gè)活躍線程數(shù),
// 并且將失活的ss保存到ctl的低三十二位
long nc = ((SP_MASK & ns) |
(UC_MASK & ((c = ctl) - AC_UNIT)));
// 用工作線程的stackPred成員保存上一個(gè)失活線程的
// scanState,從而形成一個(gè)阻塞棧,ctl的低32位保存棧頂
w.stackPred = (int)c; // hold prev stack top
// 更新當(dāng)前工作線程的scanState
U.putInt(w, QSCANSTATE, ns);
// 使用cas機(jī)制更新ctl值
if (U.compareAndSwapLong(this, CTL, c, nc))
ss = ns;
else
// 如果更新失敗則退出回滾,繼續(xù)掃描任務(wù),因?yàn)閏as過(guò)程
// 中,導(dǎo)致失敗的原因就一個(gè):ctl值發(fā)生了
// 改變,這可能是有新任務(wù)提交進(jìn)來(lái)了之后,喚醒或
// 添加了一條線程
w.scanState = ss; // back out
}
// 檢查標(biāo)識(shí)復(fù)位
checkSum = 0;
}
}
}
// 如果未掃描到任務(wù)則直接返回null,并在外邊的runWorker()發(fā)生阻塞
return null;
}
ok,如上是整個(gè)線程工作的源碼實(shí)現(xiàn),重點(diǎn)在于任務(wù)掃描的實(shí)現(xiàn)過(guò)程,同時(shí)它也是理解比較困難的一個(gè)地方,下面來(lái)整體梳理一下整個(gè)線程工作以及掃描任務(wù)的流程:
- ①線程
start()
啟動(dòng)之后會(huì)找到run()
方法,然后會(huì)開始去競(jìng)爭(zhēng)線程池中的共享任務(wù) - ②初始化線程的工作隊(duì)列,同時(shí)獲取注冊(cè)隊(duì)列時(shí)計(jì)算索引的隨機(jī)種子
- ③開啟循環(huán)掃描,通過(guò)隨機(jī)種子計(jì)算出池中隊(duì)列數(shù)組中的一個(gè)下標(biāo)索引
- ④判斷隨機(jī)出來(lái)的索引位置是否為空:
- 不為空:判斷隊(duì)列中是否存在任務(wù):
- 存在:判斷當(dāng)前線程狀態(tài)是否為活躍狀態(tài):
- 是:通過(guò)cas機(jī)制,以FIFO的模式取出棧底/隊(duì)頭的任務(wù),如果還剩余任務(wù)則新建或喚醒一條新的線程繼續(xù)處理,然后返回獲取到的任務(wù)
- 否:先將ctl中記錄的失活線程喚醒,隨機(jī)計(jì)算一個(gè)新的位置,跳出本次循環(huán),繼續(xù)下次循環(huán)
- 不存在:更新scanState并隨機(jī)計(jì)算一個(gè)新的位置,跳出本次循環(huán),繼續(xù)下次循環(huán)
- 如果存在任務(wù)但是沒(méi)有獲取到任務(wù),代表沒(méi)有有其他線程搶了任務(wù),checkSum+1,隨機(jī)計(jì)算一個(gè)新的位置,跳出本次循環(huán),繼續(xù)下次循環(huán)
- 存在:判斷當(dāng)前線程狀態(tài)是否為活躍狀態(tài):
- 為空:代表該位置不存在隊(duì)列,找到下一個(gè)位置,依次類推....
- 不為空:判斷隊(duì)列中是否存在任務(wù):
- ④如果隊(duì)列為空時(shí)會(huì)找到下一個(gè)位置,然后接著重復(fù)第④步
- ⑤如果遍歷完所有隊(duì)列還是沒(méi)有獲取到任務(wù),并且掃描期間也沒(méi)有新的任務(wù)提交到線程池時(shí),先判斷工作線程的活躍狀態(tài):
- 失活(不活躍)狀態(tài):直接退出循環(huán)回到
runWorker()
方法自旋或掛起阻塞 - 活躍狀態(tài):進(jìn)行滅活操作,利用cas機(jī)制減去ctl中一個(gè)活躍線程數(shù),同時(shí)將當(dāng)前線程的scanState值記錄到ctl的低32位做為棧頂,使用stackPred保存上一條失活線程的scanState值,從而形成一個(gè)阻塞棧
- 如果滅活操作失敗,則代表ctl發(fā)生了改變,代表有新任務(wù)提交進(jìn)了線程池,則取消滅活操作
- 線程如果是處于活躍狀態(tài),在掃描一圈沒(méi)有獲取到任務(wù)之后,會(huì)再重新掃描一次所有隊(duì)列,在第二遍掃描中線程是有機(jī)會(huì)被重新“復(fù)活(喚醒)”的
- 失活(不活躍)狀態(tài):直接退出循環(huán)回到
- ⑥當(dāng)線程第二圈掃描后,依舊未獲取到任務(wù),那么當(dāng)前線程會(huì)退出循環(huán),返回空
- ⑦掃描完畢后回到
runWorker()
方法,在該方法中會(huì)判斷掃描結(jié)果是否為空:- 非空:調(diào)用
runTask()
執(zhí)行掃描獲取到的任務(wù) - 為空:調(diào)用
awaitWork()
自旋或掛起阻塞線程,直至有新任務(wù)提交后喚醒
- 非空:調(diào)用
- ⑧如果獲取任務(wù)成功,在執(zhí)行過(guò)程中出現(xiàn)異常則報(bào)告異常信息并注銷線程
線程工作以及掃描的整個(gè)流程會(huì)比較長(zhǎng),尤其是有些小伙伴在理解scan()
方法的多次掃描有些困難,線程在第一圈掃描時(shí)未獲取到任務(wù),會(huì)先滅活然后再掃描一圈,如果第二圈掃描到了任務(wù)則會(huì)“復(fù)活”滅活線程,然后再掃描一圈。如果第二圈掃描同樣未掃描到任務(wù),那么則直接退出循環(huán)。下面來(lái)個(gè)流程圖加深理解:
在掃描的實(shí)現(xiàn)中,其實(shí)也是包含了任務(wù)竊取的實(shí)現(xiàn)的,因?yàn)樵趻呙璧倪^(guò)程中是不會(huì)區(qū)分偶數(shù)隊(duì)列和奇數(shù)隊(duì)列,而且將所有隊(duì)列都進(jìn)行掃描,只要有任務(wù)就獲取執(zhí)行,而獲取任務(wù)的方式是通過(guò)FIFO方式進(jìn)行的,代表著共享隊(duì)列中的任務(wù)獲取以及工作竊取是通過(guò)獲取隊(duì)列頭部/棧底的元素實(shí)現(xiàn)。而線程在執(zhí)行自己工作隊(duì)列中的任務(wù)時(shí),是通過(guò)LIFO的模式進(jìn)行的,是從隊(duì)列尾部/棧頂獲取任務(wù)執(zhí)行,這樣做的好處是可以避免工作竊取和本地執(zhí)行時(shí)的CAS競(jìng)爭(zhēng)。
ok,接著來(lái)看看任務(wù)執(zhí)行以及線程掛起的實(shí)現(xiàn):
// FrokJoinPool類 → runTask()方法
final void runTask(ForkJoinTask<?> task) {
// 如果任務(wù)不為空
if (task != null) {
// scanState&=~SCANNING會(huì)把scanState變成偶數(shù),表示正在執(zhí)行任務(wù)
scanState &= ~SCANNING; // mark as busy
// 執(zhí)行任務(wù)
(currentSteal = task).doExec();
// 執(zhí)行完任務(wù)后將維護(hù)偷取到的任務(wù)的成員置空
U.putOrderedObject(this, QCURRENTSTEAL, null);
// 執(zhí)行本地任務(wù):工作線程自身隊(duì)列中的任務(wù)
execLocalTasks();
ForkJoinWorkerThread thread = owner;
// 竊取任務(wù)計(jì)數(shù)
if (++nsteals < 0)
// 疊加到ForkJoinPool的stealCounter成員中
transferStealCount(pool);
// 執(zhí)行完成后,將狀態(tài)從執(zhí)行重新改為掃描狀態(tài)
scanState |= SCANNING;
// 執(zhí)行鉤子函數(shù)
if (thread != null)
thread.afterTopLevelExec();
}
}
// FrokJoinPool類 → execLocalTasks()方法
final void execLocalTasks() {
int b = base, m, s;
ForkJoinTask<?>[] a = array;
// 如果自身工作隊(duì)列中有任務(wù)
if (b - (s = top - 1) <= 0 && a != null &&
(m = a.length - 1) >= 0) {
// 如果自身隊(duì)列被指定成了FIFO模式執(zhí)行
if ((config & FIFO_QUEUE) == 0) {
for (ForkJoinTask<?> t;;) {
// 從棧頂/隊(duì)列頭部獲取任務(wù)執(zhí)行
if ((t = (ForkJoinTask<?>)U.getAndSetObject
(a, ((m & s) << ASHIFT) + ABASE, null)) == null)
break;
U.putOrderedInt(this, QTOP, s);
//執(zhí)行任務(wù)
t.doExec();
if (base - (s = top - 1) > 0)
break;
}
}
else
// 如果沒(méi)有則直接以LIFO模式從棧底獲取任務(wù)執(zhí)行
pollAndExecAll();
}
}
// FrokJoinPool類 → pollAndExecAll()方法
final void pollAndExecAll() {
// 棧底/隊(duì)列尾部獲取任務(wù)
for (ForkJoinTask<?> t; (t = poll()) != null;)
t.doExec();
}
// WorkerQueue類 → poll()方法
final ForkJoinTask<?> poll() {
ForkJoinTask<?>[] a; int b; ForkJoinTask<?> t;
// 任務(wù)隊(duì)列不為空
while ((b = base) - top < 0 && (a = array) != null) {
// 從棧底/隊(duì)列尾部取值
int j = (((a.length - 1) & b) << ASHIFT) + ABASE;
t = (ForkJoinTask<?>)U.getObjectVolatile(a, j);
// 檢查是否被其他線程搶占
if (base == b) {
if (t != null) {
// 置空
if (U.compareAndSwapObject(a, j, t, null)) {
base = b + 1;
return t;
}
}
// 如果隊(duì)列中沒(méi)有了任務(wù)則退出
else if (b + 1 == top) // now empty
break;
}
}
return null;
}
// FrokJoinPool類 → awaitWork()方法
private boolean awaitWork(WorkQueue w, int r) {
// 如果隊(duì)列已經(jīng)被注銷,直接返回
if (w == null || w.qlock < 0)
return false;
// 開啟循環(huán)(w.stackPred:上個(gè)阻塞線程的scanState值)
for (int pred = w.stackPred, spins = SPINS, ss;;) {
// 如果當(dāng)前線程被“復(fù)活/喚醒”則直接退出
if ((ss = w.scanState) >= 0)
break;
// 自旋操作:在掛起線程前會(huì)隨機(jī)自旋一段時(shí)間
else if (spins > 0) {
// 通過(guò)隨機(jī)種子以及自旋數(shù)實(shí)現(xiàn)隨機(jī)自旋
r ^= r << 6; r ^= r >>> 21; r ^= r << 7;
// 檢查前一個(gè)失活掛起的工作線程是否已經(jīng)復(fù)活
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
}
}
// 再次檢測(cè)隊(duì)列狀態(tài),是否被注銷
else if (w.qlock < 0) // recheck after spins
return false;
// 如果線程沒(méi)有被中斷
else if (!Thread.interrupted()) {
long c, prevctl, parkTime, deadline;
// 獲取活躍線程數(shù)
int ac = (int)((c = ctl) >> AC_SHIFT) + (config & SMASK);
// 如果活躍線程數(shù)<=0,可能是要關(guān)閉線程池,這里會(huì)去幫忙關(guān)閉
if ((ac <= 0 && tryTerminate(false, false)) ||
(runState & STOP) != 0) // pool terminating
return false;
// 如果活躍線程數(shù)<=0并且當(dāng)前線程是最后掛起的線程
if (ac <= 0 && ss == (int)c) { // is last waiter
// 計(jì)算出一個(gè)ctl值
prevctl = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & pred);
// 獲取總線程數(shù)
int t = (short)(c >>> TC_SHIFT); // shrink excess spares
// 如果總線數(shù)大于2,說(shuō)明掛起的線程已經(jīng)超過(guò)兩個(gè)了
// 當(dāng)前線程會(huì)被拋棄
if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl))
// 返回false后,外面的runWorker()會(huì)直接break退出,
// 從而導(dǎo)致run()結(jié)束,線程死亡
return false;
// 如果掛起的線程數(shù)<=2或者cas失敗(有線程被喚醒/復(fù)活)
// 那么則計(jì)算掛起時(shí)間,將當(dāng)前線程掛起一段時(shí)間
// 計(jì)算掛起時(shí)間
parkTime = IDLE_TIMEOUT * ((t >= 0) ? 1 : 1 - t);
// 計(jì)算結(jié)束時(shí)間
deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
}
// 如果還存在活躍線程或當(dāng)前線程不是最后被掛起的線程
else
// 將當(dāng)前線程一直掛起(這類永久掛起的線程被喚醒后,如果對(duì)
// 應(yīng)的scanState還是失活狀態(tài),這可能是線程池正在關(guān)閉了)
prevctl = parkTime = deadline = 0L;
// 獲取當(dāng)前線程
Thread wt = Thread.currentThread();
// 模仿LockSupport.park()掛起操作
U.putObject(wt, PARKBLOCKER, this); // emulate LockSupport
w.parker = wt;
// 掛起之前會(huì)再檢測(cè)一遍狀態(tài)
if (w.scanState < 0 && ctl == c) // recheck before park
// 掛起操作
U.park(false, parkTime);
U.putOrderedObject(w, QPARKER, null);
U.putObject(wt, PARKBLOCKER, null);
// 如果被復(fù)活,則直接退出循環(huán),返回true
if (w.scanState >= 0)
break;
// 如果阻塞時(shí)間不為零并且CTL值在期間沒(méi)有發(fā)生改變,那么說(shuō)明
// 在這段時(shí)間內(nèi)外部并沒(méi)有提交新的任務(wù)進(jìn)來(lái),當(dāng)前線程則會(huì)被銷毀
if (parkTime != 0L && ctl == c &&
deadline - System.nanoTime() <= 0L &&
U.compareAndSwapLong(this, CTL, c, prevctl))
return false; // shrink pool
}
}
return true;
}
先說(shuō)說(shuō)線程執(zhí)行任務(wù)的runTask()
方法,在該方法中首先會(huì)修改狀態(tài)為執(zhí)行狀態(tài),執(zhí)行完成竊取到的任務(wù)之后會(huì)再執(zhí)行自身工作隊(duì)列中的任務(wù),在執(zhí)行自身任務(wù)時(shí),除非是指定成了FIFO模式,不然默認(rèn)都是會(huì)以LIFO模式,執(zhí)行完所有任務(wù)后,會(huì)將狀態(tài)重新改為掃描狀態(tài)。整體邏輯還算比較簡(jiǎn)單。
再來(lái)談?wù)剴炱?阻塞方法awaitWork
,當(dāng)線程掃描不到任務(wù)時(shí),會(huì)先檢查自己是否需要自旋,如果需要?jiǎng)t會(huì)使用隨機(jī)種子配合實(shí)現(xiàn)隨機(jī)自旋。自旋結(jié)束后,如果池中掛起的(空閑的)線程數(shù)過(guò)多,或者外部已經(jīng)很久沒(méi)有提交新的任務(wù)進(jìn)來(lái),都會(huì)直接銷毀線程,從而達(dá)到縮減線程數(shù)的目的。
銷毀線程的實(shí)現(xiàn)也比較有意思,在前面的《線程池分析》中得知,線程池中的線程復(fù)用原理實(shí)則是通過(guò)死循環(huán)的方式卡住了
run()
方法,不讓run()
方法結(jié)束,這樣線程就不會(huì)停止。而在該方法中,當(dāng)需要縮減線程數(shù)時(shí),則會(huì)直接返回false
,讓外面的runWorker()
方法中的循環(huán)退出,從而導(dǎo)致run()
結(jié)束,讓線程正常執(zhí)行終止達(dá)到縮減線程數(shù)的目的。
二、任務(wù)的拆分與合并實(shí)現(xiàn)過(guò)程分析
在分析Fork/Join框架成員構(gòu)成時(shí),曾簡(jiǎn)單提到過(guò)fork/join()
方法,下面再來(lái)詳細(xì)分解它兩的實(shí)現(xiàn)過(guò)程,先引用一下《上篇》中的片段:
// ForkJoinTask類 → fork方法
public final ForkJoinTask<V> fork() {
Thread t;
// 判斷當(dāng)前執(zhí)行的線程是否為池中的工作線程
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
// 如果是的則直接將任務(wù)壓入當(dāng)前線程的任務(wù)隊(duì)列
((ForkJoinWorkerThread)t).workQueue.push(this);
else
// 如果不是則壓入common池中的某個(gè)工作線程的任務(wù)隊(duì)列中
ForkJoinPool.common.externalPush(this);
// 返回當(dāng)前ForkJoinTask對(duì)象,方便遞歸拆分
return this;
}
// ForkJoinTask類 → join方法
public final V join() {
int s;
// 判斷任務(wù)執(zhí)行狀態(tài)如果是非正常結(jié)束狀態(tài)
if ((s = doJoin() & DONE_MASK) != NORMAL)
// 拋出相關(guān)的異常堆棧信息
reportException(s);
// 正常執(zhí)行結(jié)束則返回執(zhí)行結(jié)果
return getRawResult();
}
// ForkJoinTask類 → doJoin方法
private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
// status<0則直接返回status值
return (s = status) < 0 ? s :
// 判斷當(dāng)前線程是否為池中的工作線程
((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
// 是則取出線程任務(wù)隊(duì)列中的當(dāng)前task執(zhí)行,執(zhí)行完成返回status值
(w = (wt = (ForkJoinWorkerThread)t).workQueue).
// 嘗試將棧頂任務(wù)置空,然后執(zhí)行任務(wù)
tryUnpush(this) && (s = doExec()) < 0 ? s :
// 執(zhí)行未完成則調(diào)用awaitJoin方法等待執(zhí)行完成
wt.pool.awaitJoin(w, this, 0L) :
// 不是則調(diào)用externalAwaitDone()方法阻塞掛起當(dāng)前線程
// 將任務(wù)交由通用的common線程池執(zhí)行
externalAwaitDone();
}
-
fork方法邏輯:
- ①判斷當(dāng)前線程是否為池中的工作線程類型
- 是:將當(dāng)前任務(wù)壓入當(dāng)前線程的任務(wù)隊(duì)列中
- 不是:將當(dāng)前任務(wù)壓入common池中某個(gè)工作線程的任務(wù)隊(duì)列中
- ②返回當(dāng)前的ForkJoinTask任務(wù)對(duì)象,方便遞歸拆分
- ①判斷當(dāng)前線程是否為池中的工作線程類型
-
doJoin&join方法邏輯:
- ①判斷任務(wù)狀態(tài)status是否小于0:
- 小于:代表任務(wù)已經(jīng)結(jié)束,返回status值
- 不小于:判斷當(dāng)前線程是否為池中的工作線程:
- 是:嘗試從棧頂/隊(duì)尾取出當(dāng)前task執(zhí)行:
- 任務(wù)在棧頂:執(zhí)行任務(wù)并返回執(zhí)行結(jié)束的status值
- 不在棧頂:調(diào)用awaitJoin方法等待執(zhí)行結(jié)束
- 不是:調(diào)用externalAwaitDone()方法阻塞掛起當(dāng)前線程,等待任務(wù)執(zhí)行結(jié)束
- 是:嘗試從棧頂/隊(duì)尾取出當(dāng)前task執(zhí)行:
- ②判斷任務(wù)執(zhí)行狀態(tài)是否為非正常結(jié)束狀態(tài),是則拋出異常堆棧信息
- 任務(wù)狀態(tài)為被取消,拋出CancellationException異常
- 任務(wù)狀態(tài)為異常結(jié)束,拋出對(duì)應(yīng)的執(zhí)行異常信息
- ③如果status為正常結(jié)束狀態(tài),則直接返回執(zhí)行結(jié)果
- ①判斷任務(wù)狀態(tài)status是否小于0:
關(guān)于doJoin方法的代碼可能看起來(lái)有些難理解,還是和前面上篇中分析“工作線程注冊(cè)的原理時(shí),理解奇數(shù)位索引計(jì)算”的方式一樣,自己寫一遍理解,換個(gè)寫法如下:
private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt;
ForkJoinPool.WorkQueue w;
// 如果任務(wù)已經(jīng)執(zhí)行完成,直接返回任務(wù)狀態(tài)
if ((s = status) < 0) {
return s;
}
t = Thread.currentThread();
boolean isForkJoinThread = t instanceof ForkJoinWorkerThread;
// 如果當(dāng)前線程不是工作線程,即外部線程直接調(diào)用join方法合并
if (!isForkJoinThread) {
// 等待任務(wù)被線程池分配線程執(zhí)行完,返回任務(wù)狀態(tài)
return externalAwaitDone();
}
// 如果當(dāng)前線程是工作線程
wt = (ForkJoinWorkerThread) t;
w = wt.workQueue;
// 如果當(dāng)前任務(wù)在隊(duì)列尾部/棧頂,直接彈出來(lái)
if (w.tryUnpush(this)) {
// 然后執(zhí)行彈出來(lái)的任務(wù)
return this.doExec();
}
// 如果當(dāng)前任務(wù)不在隊(duì)列尾部/棧頂,那么調(diào)用awaitJoin等待
return wt.pool.awaitJoin(w, this, 0L);
}
經(jīng)過(guò)這樣就可以非常清晰的看明白doJoin
方法的邏輯啦。接著往下分析,其實(shí)fork的原理實(shí)現(xiàn)還算簡(jiǎn)單,下面重點(diǎn)分析join
的實(shí)現(xiàn)。先看看tryUnpush()
方法:
// ForkJoinTask類 → tryUnpush()方法
final boolean tryUnpush(ForkJoinTask<?> t) {
ForkJoinTask<?>[] a; int s;
// 嘗試將棧頂/隊(duì)尾任務(wù)置空,如果t就是隊(duì)列中的棧頂任務(wù),那嘗試cas置空
if ((a = array) != null && (s = top) != base &&
U.compareAndSwapObject
(a, (((a.length - 1) & --s) << ASHIFT) + ABASE, t, null)) {
U.putOrderedInt(this, QTOP, s);
return true;
}
return false;
}
工作線程在合并結(jié)果時(shí),如果這個(gè)任務(wù)被fork到了棧頂/隊(duì)尾,那么執(zhí)行該任務(wù)返回即可。但如果不在棧頂,有可能是被其他fork出的任務(wù)壓下去了或者其他線程被竊取了,那么則會(huì)進(jìn)入awaitJoin()
方法。
2.1、awaitJoin方法
接著來(lái)看看awaitJoin()
方法,源碼如下:
// ForkJoinTask類 → awaitJoin()方法
final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
int s = 0;
if (task != null && w != null) {
// 記錄前一個(gè)正在合并的任務(wù)
ForkJoinTask<?> prevJoin = w.currentJoin;
// 記錄join合并當(dāng)前任務(wù)
U.putOrderedObject(w, QCURRENTJOIN, task);
// CountedCompleter是ForkJoinTask的一個(gè)子類實(shí)現(xiàn)
CountedCompleter<?> cc = (task instanceof CountedCompleter) ?
(CountedCompleter<?>)task : null;
// 自旋操作
for (;;) {
// 1.任務(wù)已經(jīng)執(zhí)行完畢,不需要再自旋了,直接返回
if ((s = task.status) < 0)
break;
// 如果任務(wù)是CountedCompleter類型,則獲取它的派生子任務(wù)執(zhí)行
if (cc != null)
helpComplete(w, cc, 0);
// 如果隊(duì)列不為空,嘗試從隊(duì)列中獲取當(dāng)前需要join的任務(wù)執(zhí)行。
// 如果當(dāng)前隊(duì)列任務(wù)為空,說(shuō)明當(dāng)前任務(wù)被其他工作線程給竊取了
// tryRemoveAndExec是用于嘗試執(zhí)行存到隊(duì)列中的當(dāng)前任務(wù),
// 如果隊(duì)列中沒(méi)有找到當(dāng)前join的任務(wù),那代表被其他線程給偷走了
else if (w.base == w.top || w.tryRemoveAndExec(task))
// 找到竊取join任務(wù)的工作線程,幫助竊取者執(zhí)行竊取者的任務(wù)
helpStealer(w, task);
// 3.再判斷一次任務(wù)是否已經(jīng)執(zhí)行完畢,執(zhí)行結(jié)束則退出
// 如果任務(wù)被竊取,能夠執(zhí)行到這一步,那么一定是前面的
// helpStealer方法退出了,原因有兩個(gè):
// 1.自己需要join合并的任務(wù)執(zhí)行完了
// 2.竊取鏈斷了或沒(méi)有可竊取的任務(wù)了,準(zhǔn)備阻塞
if ((s = task.status) < 0)
break;
long ms, ns;
if (deadline == 0L)
ms = 0L;
else if ((ns = deadline - System.nanoTime()) <= 0L)
break;
else if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) <= 0L)
ms = 1L;
// 4.調(diào)用tryCompensate方法對(duì)線程池進(jìn)行補(bǔ)償
// 進(jìn)入阻塞之前為了避免線程池所有線程都進(jìn)入阻塞,
// 會(huì)為線程池補(bǔ)償一個(gè)活躍線程(喚醒或新建)
if (tryCompensate(w)) {
// 自旋加阻塞,等待其他線程執(zhí)行完成竊取的join任務(wù)
task.internalWait(ms);
// 喚醒后疊加活躍線程數(shù)
U.getAndAddLong(this, CTL, AC_UNIT);
}
}
// 當(dāng)任務(wù)執(zhí)行完成后,將currentJoin恢復(fù)成之前的currentJoin值
U.putOrderedObject(w, QCURRENTJOIN, prevJoin);
}
return s;
}
// ForkJoinTask類 → tryRemoveAndExec()方法
final boolean tryRemoveAndExec(ForkJoinTask<?> task) {
ForkJoinTask<?>[] a; int m, s, b, n;
// 如果隊(duì)列中的任務(wù)數(shù)組不為空且已經(jīng)初始化
if ((a = array) != null && (m = a.length - 1) >= 0 &&
task != null) {
// 隊(duì)列中是否存在任務(wù)
while ((n = (s = top) - (b = base)) > 0) {
for (ForkJoinTask<?> t;;) { // traverse from s to b
// 從棧頂開始往下取值
long j = ((--s & m) << ASHIFT) + ABASE;
// 因?yàn)榇嬖诓l(fā),可能會(huì)被竊取者偷走任務(wù)
if ((t = (ForkJoinTask<?>)U.getObject(a, j)) == null)
// 如果發(fā)生了任務(wù)竊取,那說(shuō)明此時(shí)的s已經(jīng)執(zhí)行到了棧底
// 如果被偷走join任務(wù)是在棧頂被偷走的,那么將返回true
return s + 1 == top; // shorter than expected
// 如果找到了任務(wù)
else if (t == task) {
boolean removed = false;
// 當(dāng)前join任務(wù)在棧頂,嘗試將其彈出
// 如果cas失敗,代表被其他線程偷走,此時(shí)隊(duì)列已經(jīng)空了
if (s + 1 == top) { // pop
if (U.compareAndSwapObject(a, j, task, null)) {
U.putOrderedInt(this, QTOP, s);
removed = true;
}
}
// 當(dāng)前join任務(wù)不在棧頂并且棧底沒(méi)變,
// 將當(dāng)前join任務(wù)的坑位替換成EmptyTask對(duì)象
else if (base == b) // replace with proxy
// 因?yàn)槿蝿?wù)不在棧頂,不能直接替換成null,
// 替換成null就必須移動(dòng)指針,顯然這里不能移動(dòng)指針
// 很多地方都是以null作為并發(fā)判斷,
// 其他工作線程取到null時(shí),
// 會(huì)認(rèn)為任務(wù)被其他線程竊取了任務(wù),
// 這樣就永遠(yuǎn)獲取不到任務(wù)了
removed = U.compareAndSwapObject(
a, j, task, new EmptyTask());
if (removed)
//執(zhí)行任務(wù)
task.doExec();
break;
}
// 如果其他任務(wù)已經(jīng)執(zhí)行完成,并且是棧頂任務(wù),那么置空
else if (t.status < 0 && s + 1 == top) {
if (U.compareAndSwapObject(a, j, t, null))
U.putOrderedInt(this, QTOP, s);
break; // was cancelled
}
// 從棧頂找到棧底都沒(méi)有找到,返回false
// 雖然任務(wù)被偷了,但是也不去參與helpStealer了
if (--n == 0)
return false;
}
// 任務(wù)已經(jīng)完成
if (task.status < 0)
return false;
}
}
return true;
}
awaitJoin
方法的總體邏輯還算簡(jiǎn)單,如下:
- ①檢查當(dāng)前線程的工作隊(duì)列是否為空
- 為空:代表任務(wù)被竊取了
- 不為空:通過(guò)
tryRemoveAndExec
在整個(gè)隊(duì)列中查找當(dāng)前需要join的任務(wù)- 找到了:執(zhí)行任務(wù)
- 沒(méi)找到:代表任務(wù)還是被偷了(這種情況下不參與
helpStealer
方法)
- ②如果任務(wù)被偷了,那么通過(guò)
helpStealer
找到竊取者,幫助它執(zhí)行任務(wù) - ③如果從
helpStealer
方法中退出,會(huì)再檢查一次任務(wù)是否已完成:- 已執(zhí)行結(jié)束:退出循環(huán),出去合并結(jié)果
- 未執(zhí)行結(jié)束:準(zhǔn)備進(jìn)入阻塞,避免CPU資源浪費(fèi)
- ④在進(jìn)入阻塞之前,會(huì)先對(duì)線程池進(jìn)行補(bǔ)償,因?yàn)楫?dāng)前線程可能是線程池中的最后一個(gè)活躍線程,為了避免線程池所有線程都“死”掉,會(huì)先為線程池補(bǔ)償一個(gè)活躍線程
ok~,再來(lái)看看tryRemoveAndExec
方法的邏輯,如下:
- 判斷隊(duì)列中是否有任務(wù):
- 不存在:返回true,外部的
awaitJoin
方法進(jìn)入helpStealer
邏輯 - 存在:判斷任務(wù)是否在隊(duì)列尾部/棧底:
- 在:嘗試CAS彈出棧頂任務(wù):
- 成功:執(zhí)行任務(wù)
- 失敗:代表CAS失敗,任務(wù)被別的線程偷走了,進(jìn)入
helpStealer
邏輯
- 不在:可能被其他任務(wù)壓下去了,從棧頂開始查找整個(gè)隊(duì)列:
- 找到了:將任務(wù)替換成
EmptyTask
對(duì)象,執(zhí)行任務(wù) - 沒(méi)找到:代表任務(wù)被偷了,但雖然沒(méi)找到,也不參與
helpStealer
了,不過(guò)在退出之前會(huì)再一次檢測(cè)任務(wù)是否執(zhí)行完成
- 找到了:將任務(wù)替換成
- 在:嘗試CAS彈出棧頂任務(wù):
- 不存在:返回true,外部的
tryRemoveAndExec
方法比較簡(jiǎn)單,該方法主要作用是遍歷當(dāng)前線程的WorkQueue
,在隊(duì)列中查找要join合并的任務(wù)執(zhí)行。而在執(zhí)行過(guò)程中,如果隊(duì)列為空或者任務(wù)在棧頂?shù)玞as失敗以及遍歷完整個(gè)隊(duì)列都沒(méi)找到要join的任務(wù),這三種情況代表任務(wù)被偷了,對(duì)于前兩種情況下,會(huì)進(jìn)入helpStealer
幫助竊取者執(zhí)行任務(wù),而對(duì)于最后一種被竊取任務(wù)的情況,則會(huì)直接退出阻塞(個(gè)人猜測(cè):可能是因?yàn)楸闅v完整個(gè)隊(duì)列會(huì)導(dǎo)致一段時(shí)間的開銷,被竊取走的任務(wù)很有可能在這段時(shí)間內(nèi)已經(jīng)執(zhí)行完了或快執(zhí)行完了。所以與其去幫助竊取者執(zhí)行任務(wù),還不如阻塞等待一會(huì)兒)。
2.2、helpStealer幫助竊取者執(zhí)行方法
再來(lái)看看helpStealer
方法,源碼如下:
// ForkJoinTask類 → helpStealer()方法
private void helpStealer(WorkQueue w, ForkJoinTask<?> task) {
WorkQueue[] ws = workQueues;
int oldSum = 0, checkSum, m;
// 如果隊(duì)列數(shù)組和任務(wù)隊(duì)列不為空
if (ws != null && (m = ws.length - 1) >= 0 && w != null &&
task != null) {
do { // restart point
checkSum = 0; // for stability check
ForkJoinTask<?> subtask;
WorkQueue j = w, v; // v is subtask stealer
descent: for (subtask = task; subtask.status >= 0; ) {
// j.hint開始是j隊(duì)列在隊(duì)列組中的用于計(jì)算下標(biāo)的隨機(jī)值,
// 如果找到了竊取者,這個(gè)值會(huì)變成對(duì)應(yīng)竊取者的下標(biāo)
// j.hint | 1=一個(gè)奇數(shù),k += 2:步長(zhǎng)為2,奇數(shù)+2=奇數(shù)
for (int h = j.hint | 1, k = 0, i; ; k += 2) {
// 查找完整個(gè)對(duì)應(yīng)數(shù)組的所有奇數(shù)位,
// 如果還是沒(méi)有找到任務(wù),則直接退出(可能執(zhí)行完成了)
if (k > m) // can't find stealer
break descent;
//(h + k) & m:計(jì)算出一個(gè)數(shù)組之內(nèi)的奇數(shù)下標(biāo),
// 檢查這個(gè)下標(biāo)的隊(duì)列是否偷走了自己的任務(wù)
if ((v = ws[i = (h + k) & m]) != null) {
// 判斷currentSteal是否是當(dāng)前的任務(wù)
if (v.currentSteal == subtask) {
// 是的,記錄這個(gè)偷取者在隊(duì)列組的下標(biāo)
j.hint = i;
break;
}
// 檢查了一個(gè)隊(duì)列之后會(huì)計(jì)入校驗(yàn)和
checkSum += v.base;
}
}
// 當(dāng)前線程幫竊取者線程執(zhí)行任務(wù)
for (;;) { // help v or descend
ForkJoinTask<?>[] a; int b;
// 將竊取者線程隊(duì)列棧底也計(jì)入校驗(yàn)和,因?yàn)樗`取了任務(wù)
// ,很有可能fork出更小的任務(wù)然后被其他線程偷走
checkSum += (b = v.base);
// 獲取竊取者當(dāng)前正在join的任務(wù)
ForkJoinTask<?> next = v.currentJoin;
//subtask.status < 0 任務(wù)執(zhí)行完成
// 如果任務(wù)執(zhí)行結(jié)果
// 或者工作線程要合并的任務(wù)已經(jīng)不是subtask了
// 或者竊取者竊取的任務(wù)已經(jīng)不為當(dāng)前join任務(wù)了
// 那么退出循環(huán)
if (subtask.status < 0 || j.currentJoin != subtask ||
v.currentSteal != subtask) // stale
break descent;
// 如果當(dāng)前線程中沒(méi)有任務(wù),則會(huì)幫它join合并任務(wù)
// 在這里會(huì)對(duì)subtask重新賦值,如果為空則會(huì)回到descent
// 循環(huán)進(jìn)行下一個(gè)迭代
if (b - v.top >= 0 || (a = v.array) == null) {
// 如果竊取者不需要join合并任務(wù),
// 退出判斷任務(wù)是否結(jié)束
if ((subtask = next) == null)
break descent;
// 如果竊取者有任務(wù)要join合并,
// 那將幫竊取者去找偷它任務(wù)的竊取者
j = v;
break;
}
// 如果竊取者的隊(duì)列中有任務(wù),從棧底/隊(duì)頭開始偷竊取者
// 線程的任務(wù)執(zhí)行(可能竊取到自身被偷的任務(wù)
// fork出來(lái)的子任務(wù)),
int i = (((a.length - 1) & b) << ASHIFT) + ABASE;
ForkJoinTask<?> t = ((ForkJoinTask<?>)
U.getObjectVolatile(a, i));
// 偷完之后看一下棧底/隊(duì)頭有沒(méi)有發(fā)生變化,
// 如果變了,代表有其他線程也在偷竊取者線程的任務(wù),
// 避免無(wú)效的cas,直接重新再偷一個(gè)新的任務(wù)
if (v.base == b) {
// ==null,代表任務(wù)被其他線程偷了,
// 然后賦值成了null,只是還沒(méi)來(lái)得及將base更新
if (t == null) // stale
// 回到 descent 標(biāo)志進(jìn)行下一個(gè)迭代
break descent;
// 如果沒(méi)變則cas置空棧底/隊(duì)頭的任務(wù)
// 這樣可以告訴別的線程當(dāng)前任務(wù)已被竊取
if (U.compareAndSwapObject(a, i, t, null)) {
// 更新棧底指針
v.base = b + 1;
// 記錄自己前一個(gè)偷取的任務(wù)
ForkJoinTask<?> ps = w.currentSteal;
int top = w.top;
do {
// 將新偷到的任務(wù)更新到currentSteal中
U.putOrderedObject(w, QCURRENTSTEAL, t);
// 執(zhí)行竊取到的任務(wù)
t.doExec(); // clear local tasks too
// 在join的任務(wù)還未執(zhí)行完成的情況下,
// 并且剛才執(zhí)行的任務(wù)發(fā)生了fork任務(wù),
// 那么w.top !=top就會(huì)成立,
// 此時(shí)就得w.pop()執(zhí)行本地任務(wù)
} while (task.status >= 0 &&
w.top != top &&
(t = w.pop()) != null);
// 執(zhí)行結(jié)束后恢復(fù)原本的竊取記錄
U.putOrderedObject(w, QCURRENTSTEAL, ps);
// 然后再看看自身隊(duì)列中有沒(méi)有任務(wù)
// 如果w.base != w.top成立,代表自身隊(duì)列來(lái)了
// 任務(wù),此時(shí)則直接結(jié)束,回去執(zhí)行自己的任務(wù),
// 沒(méi)有必要幫別的線程執(zhí)行任務(wù)了
if (w.base != w.top)
return; // can't further help
}
}
}
}
// 退出helpStealer的條件有兩個(gè):
// 1.自己需要合并的join任務(wù)執(zhí)行完了,回去執(zhí)行自己的合并任務(wù);
// 2.自己的join任務(wù)沒(méi)執(zhí)行完,但已經(jīng)竊取不到任務(wù)了,那退出阻塞
// 當(dāng)前線程,因?yàn)槔^續(xù)找下去也是空跑,浪費(fèi)CPU資源
} while (task.status >= 0 && oldSum != (oldSum = checkSum));
}
}
該方法是ForkJoin
框架實(shí)現(xiàn)“工作竊取思想”的核心體現(xiàn)。它與scan
掃描方法完成了整個(gè)框架“工作竊取”實(shí)現(xiàn)。在scan
方法之后的runTask
方法中,會(huì)對(duì)currentSteal
賦值,而helpStealer
方法就是依賴于該成員與currentJoin
成員形成的一條竊取鏈,實(shí)現(xiàn)了幫助竊取者執(zhí)行任務(wù),關(guān)于helpStealer
的具體邏輯則不再分析了,大家可以參考上述源碼中的注釋。
總而言之,
helpStealer
方法的核心思想是幫助執(zhí)行,幫助竊取者執(zhí)行它的任務(wù),但它不僅僅只會(huì)幫助竊取者執(zhí)行,還會(huì)基于currentSteal
與currentJoin
成員形成的竊取鏈幫助竊取者的竊取者執(zhí)行、幫助竊取者的竊取者的竊取者執(zhí)行、幫助竊取者.....的竊取者執(zhí)行任務(wù)。上個(gè)例子理解,如下:
- ①線程T1需要join合并任務(wù)TaskA,但是TaskA被偷了,開始遍歷查找所有奇數(shù)隊(duì)列
- ②查找后發(fā)現(xiàn)TaskA==線程T2.currentSteal成員,此時(shí)T2為T1的竊取者
- ③T1從T2的隊(duì)列棧底竊取一個(gè)任務(wù)執(zhí)行,執(zhí)行完再竊取一個(gè)執(zhí)行,繼續(xù)竊取....
- ④T1發(fā)現(xiàn)T2的隊(duì)列中沒(méi)有了任務(wù),T1則會(huì)繼續(xù)尋找竊取了T2.currentlJoin的線程
- ⑤經(jīng)過(guò)遍歷發(fā)現(xiàn)T2.currentlJoin==T5.currentSteal,T5為T2的竊取者
- ⑥然后T1繼續(xù)從T5隊(duì)列的棧底竊取一個(gè)任務(wù)執(zhí)行,完成后繼續(xù)竊取.....
- ⑦T1發(fā)現(xiàn)T5的隊(duì)列中也沒(méi)有了任務(wù),T1會(huì)繼續(xù)尋找竊取了T5.currentlJoin的....
- ⑧根據(jù)竊取鏈,一直這樣循環(huán)下去.....
通過(guò)如上過(guò)程可發(fā)現(xiàn):T1.currentlJoin → T2.currentSteal → T2.currentlJoin → T5.currentSteal → T5.currentlJoin....
,通過(guò)currentSteal
與currentJoin
兩個(gè)成員構(gòu)成了一條竊取鏈,如果理解了這條鏈路關(guān)系,那么也就理解了helpStealer
方法。不過(guò)值得注意的是:helpStealer
方法什么時(shí)候退出呢?答案是:竊取鏈斷掉的時(shí)候會(huì)退出。總共有三種情況會(huì)導(dǎo)致竊取鏈斷掉:
- ①任何一個(gè)工作線程的
currentSteal
或currentJoin
為空 - ②任何一個(gè)工作線程的
currentSteal
或currentJoin
已經(jīng)執(zhí)行完成 - ③當(dāng)前線程的join任務(wù)已經(jīng)執(zhí)行完成
其實(shí)說(shuō)到底,helpStealer
方法是ForkJoin
框架的一個(gè)優(yōu)化性能的實(shí)現(xiàn)點(diǎn),核心點(diǎn)在于減少線程因?yàn)楹喜⒍枞诘却齤oin任務(wù)執(zhí)行期間幫其它線程執(zhí)行一個(gè)任務(wù),這樣則保證了每個(gè)線程不停止工作,也能夠加快整體框架的處理速度,同時(shí)在幫助執(zhí)行的期間,被竊取的join任務(wù)就執(zhí)行完了。
2.3、tryCompensate補(bǔ)償活躍線程方法
再來(lái)看看為線程池補(bǔ)償活躍線程的tryCompensate
方法:
// ForkJoinPool類 → tryCompensate()方法
private boolean tryCompensate(WorkQueue w) {
boolean canBlock;
WorkQueue[] ws; long c; int m, pc, sp;
// 如果線程池已經(jīng)停止,處于terminate狀態(tài),不能阻塞,也不需要阻塞
if (w == null || w.qlock < 0 || // caller terminating
(ws = workQueues) == null || (m = ws.length - 1) <= 0 ||
(pc = config & SMASK) == 0) // parallelism disabled
canBlock = false;
// 如果ctl的低32位中有掛起的空閑線程,那么嘗試喚醒它,成功則阻塞自己
// 喚醒后在一定程度上也許會(huì)執(zhí)行到自己被偷的任務(wù)fork出的子任務(wù)
// tryRelease第二個(gè)參數(shù)為0,當(dāng)喚醒成功后,代表當(dāng)前線程將被阻塞,
// 新的空閑線程被喚醒,所以沒(méi)必要先減少活躍線程數(shù),然后再加上
else if ((sp = (int)(c = ctl)) != 0) // release idle worker
canBlock = tryRelease(c, ws[sp & m], 0L);
// 如果沒(méi)有空閑線程,就要?jiǎng)?chuàng)建新的線程
// 這里會(huì)導(dǎo)致線程池中的線程數(shù),在一段時(shí)間內(nèi)會(huì)超過(guò)創(chuàng)建時(shí)指定的并行數(shù)
else {
// 獲取池中的活躍線程數(shù)
int ac = (int)(c >> AC_SHIFT) + pc;
// 獲取池中的總線程數(shù)
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;
// 找奇數(shù)位置的隊(duì)列,循環(huán)m次就是執(zhí)行了兩遍。
// 為什么執(zhí)行兩遍呢?主要是為了判斷穩(wěn)定性,有可能第二遍
// 的時(shí)候,正在執(zhí)行任務(wù)的活躍線程會(huì)變少
if ((v = ws[((i << 1) | 1) & m]) != null) {
// 檢查工作線程是否正在處理任務(wù),
// 如果不在處理任務(wù)表示空閑,可以獲取其他任務(wù)執(zhí)行
if ((v.scanState & SCANNING) != 0)
break;
++nbusy;
}
}
// 如果線程池狀態(tài)不穩(wěn)定,那么則不能掛起當(dāng)前線程
// 如果nbusy!=tc*2 說(shuō)明還存在空閑或者還在掃描任務(wù)的工作線程
// 如果ctl!=c 代表ctl發(fā)生了改變,有可能線程執(zhí)行完任務(wù)后,
// 沒(méi)有掃描到新的任務(wù)被失活,這種情況下先不掛起,先自旋一段時(shí)間
if (nbusy != (tc << 1) || ctl != c)
canBlock = false; // unstable or stale
// tc:池內(nèi)總線程數(shù) pc:并行數(shù) ac:池內(nèi)活躍線程數(shù)
// tc>=pc 代表此時(shí)線程數(shù)已經(jīng)夠多了,當(dāng)然并不代表不會(huì)創(chuàng)建新線程
// ac>1 代表除了自己外還有其他活躍線程
// w.isEmpty() 當(dāng)前工作線程隊(duì)列為空,其中沒(méi)有任務(wù)需要執(zhí)行
// 如果滿足如上三個(gè)條件,那么則可以直接阻塞,不需要補(bǔ)償
else if (tc >= pc && ac > 1 && w.isEmpty()) {
long nc = ((AC_MASK & (c - AC_UNIT)) |
(~AC_MASK & c)); // uncompensated
//cas ctl
canBlock = U.compareAndSwapLong(this, CTL, c, nc);
}
// 這是對(duì)于commonPool 公共線程池的特殊處理
// 如果總線程數(shù)超出MAX_CAP則會(huì)拋出異常
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
// 準(zhǔn)備創(chuàng)建新的工作線程(這里只加總線程數(shù),不加活躍線程數(shù))
// 因?yàn)楫?dāng)前工作線程將在創(chuàng)建補(bǔ)償線程成功之后阻塞
// 但是這里會(huì)導(dǎo)致總線程數(shù)超出并行數(shù)
long nc = ((AC_MASK & c) |
(TC_MASK & (c + TC_UNIT)));
// 線程池沒(méi)有停止的情況下才允許創(chuàng)建新的工作線程
if (((rs = lockRunState()) & STOP) == 0)
add = U.compareAndSwapLong(this, CTL, c, nc);
unlockRunState(rs, rs & ~RSLOCK);
// 創(chuàng)建新的工作線程
canBlock = add && createWorker(); // throws on exception
}
}
return canBlock;
}
該方法內(nèi)的邏輯也算比較簡(jiǎn)單:
- ①判斷池內(nèi)有沒(méi)有掛起的空閑線程,如果有則喚醒它代替自己
- ②如果沒(méi)有掛起的空閑線程,判斷池內(nèi)活躍線程數(shù)是否存在兩個(gè)及以上、總線程數(shù)是否飽和、自己工作隊(duì)列是否為空,如果這些都滿足,那么則不需要補(bǔ)償,直接掛起
- ③如果不滿足上述三條件,判斷線程數(shù)是否關(guān)閉,如果沒(méi)有則創(chuàng)建新線程補(bǔ)償
值得一提的是:tryCompensate
方法會(huì)導(dǎo)致一段時(shí)間內(nèi),池中總線程數(shù)超出創(chuàng)建線程池時(shí)指定的并行數(shù)。而且如果在用Fork/Join
框架時(shí),如果在ForkJoinTask中調(diào)用提交任務(wù)的方法:sumbit()/invoke()/execute()
時(shí),會(huì)導(dǎo)致線程池一直補(bǔ)償線程,硬件允許的情況下會(huì)導(dǎo)致一直補(bǔ)償創(chuàng)建出最大0x7fff = 32767
條線程。
2.4、externalAwaitDone方法
前面分析doJoin
邏輯提到過(guò):如果是外部線程調(diào)用join
方法時(shí),會(huì)調(diào)用externalAwaitDone
方法,接著再來(lái)看看這個(gè)方法:
// ForkJoinPool類 → externalAwaitDone()方法
private int externalAwaitDone() {
// 如果任務(wù)是CountedCompleter類型,嘗試使用common池去外部幫助執(zhí)行,
// 執(zhí)行完成后并將完成任務(wù)狀態(tài)返回
int s = ((this instanceof CountedCompleter) ? // try helping
ForkJoinPool.common.externalHelpComplete(
(CountedCompleter<?>)this, 0) :
// 當(dāng)前task不是CountedCompleter,嘗試從棧頂獲取到當(dāng)前
// join的任務(wù)交給common池執(zhí)行,如果不在棧頂,s變?yōu)?
ForkJoinPool.common.tryExternalUnpush(this) ? doExec() : 0);
// 如果s>=0,那代表任務(wù)是未結(jié)束的狀態(tài),需要阻塞
if (s >= 0 && (s = status) >= 0) {
boolean interrupted = false;
do {
// 先設(shè)置SIGNAL信號(hào)標(biāo)記,通知其他線程當(dāng)前需要被喚醒
if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
// 通過(guò)synchronized.wait()掛起線程
synchronized (this) {
if (status >= 0) { // 雙重檢測(cè)
try {
wait(0L); // 掛起線程
} catch (InterruptedException ie) {
interrupted = true;
}
}
else
// 如果發(fā)現(xiàn)已完成,則喚醒所有等待線程
notifyAll();
}
}
// task未完成會(huì)一直循環(huán)
} while ((s = status) >= 0);
// 響應(yīng)中斷操作
if (interrupted)
Thread.currentThread().interrupt();
}
// 執(zhí)行完成后返回執(zhí)行狀態(tài)
return s;
}
externalAwaitDone
方法最簡(jiǎn)單,如果任務(wù)在棧頂,那么直接彈出執(zhí)行,如果不在則掛起當(dāng)前線程,直至任務(wù)執(zhí)行結(jié)束,其他線程喚醒。
2.5、任務(wù)拆分合并原理總結(jié)
任務(wù)的fork操作比較簡(jiǎn)單,只需要將拆分好的任務(wù)push進(jìn)入自己的工作隊(duì)列即可。而對(duì)于任務(wù)結(jié)果的合并:join操作,實(shí)現(xiàn)就略顯復(fù)雜了,大體思想是首先在自己隊(duì)列中找需要join的任務(wù),如果找到了則執(zhí)行它并合并結(jié)果。如果沒(méi)找到就是被偷了,需要去找竊取者線程,并且在join任務(wù)執(zhí)行結(jié)束之前,會(huì)根據(jù)竊取鏈一直幫助竊取者執(zhí)行任務(wù),如果竊取鏈斷了但是join任務(wù)還未執(zhí)行完,那么掛起當(dāng)前工作線程,不過(guò)在掛起之前會(huì)根據(jù)情況來(lái)決定是否為線程池補(bǔ)償一條活躍線程代替自己工作,防止整個(gè)線程池所有的線程都阻塞,產(chǎn)生線程池“假死”狀態(tài)。當(dāng)然,如果是外部線程執(zhí)行的join操作,如果要被join的任務(wù)還未執(zhí)行完的情況下,那么則需要把這任務(wù)交給commonPool
公共池來(lái)處理。
三、ForkJoin中任務(wù)取消實(shí)現(xiàn)原理
任務(wù)取消的cancel
方法是實(shí)現(xiàn)于Future
接口的,邏輯比較簡(jiǎn)單,源碼如下:
// ForkJoinTask類 → cancel()方法
public boolean cancel(boolean mayInterruptIfRunning) {
// 嘗試將任務(wù)狀態(tài)修改為CANCELLED,成功返回true,失敗返回false
return (setCompletion(CANCELLED) & DONE_MASK) == CANCELLED;
}
// ForkJoinTask類 → setCompletion()方法
private int setCompletion(int completion) {
// 開啟自旋(死循環(huán))
for (int s;;) {
// 如果任務(wù)已經(jīng)完成,則直接返回執(zhí)行后的狀態(tài)
if ((s = status) < 0)
return s;
// 如果還未完成則嘗試通過(guò)cas機(jī)制修改狀態(tài)為入?yún)ⅲ篶ompletion狀態(tài)
if (U.compareAndSwapInt(this, STATUS, s, s | completion)) {
if ((s >>> 16) != 0)
synchronized (this) { notifyAll(); }
return completion;
}
}
}
取消任務(wù)的邏輯比較簡(jiǎn)單,任務(wù)取消只能發(fā)生在任務(wù)還未被執(zhí)行的情況下,如果任務(wù)已經(jīng)完成則會(huì)直接返回執(zhí)行狀態(tài)。如果任務(wù)還未執(zhí)行,則會(huì)嘗試使用自旋+CAS機(jī)制修改任務(wù)狀態(tài)為CANCELLED
狀態(tài),成功則代表任務(wù)取消成功。
四、ForkJoinPool線程池的關(guān)閉實(shí)現(xiàn)
一般在正常關(guān)閉線程池時(shí),都會(huì)通過(guò)shundown
方法來(lái)停止線程池,接著再分析一下線程池關(guān)閉的實(shí)現(xiàn):
// ForkJoinPool類 → shutdown()方法
public void shutdown() {
// 檢查權(quán)限
checkPermission();
// 關(guān)閉線程池
tryTerminate(false, true);
}
// ForkJoinPool類 → checkPermission()方法
private static void checkPermission() {
// 獲取權(quán)限管理器
SecurityManager security = System.getSecurityManager();
// 檢測(cè)當(dāng)前線程是否具備關(guān)閉線程池的權(quán)限
if (security != null)
security.checkPermission(modifyThreadPermission);
}
// ForkJoinPool類 → tryTerminate()方法
private boolean tryTerminate(boolean now, boolean enable) {
int rs;
// 如果是common公開池,不能關(guān)閉,common的關(guān)閉和Java程序綁定
if (this == common) // cannot shut down
return false;
// 如果線程池還在運(yùn)行,那么檢測(cè)enable是否為true,如果是false則退出
if ((rs = runState) >= 0) {
if (!enable)
return false;
rs = lockRunState(); // enter SHUTDOWN phase
// 如果線程池是要關(guān)閉,首先把運(yùn)行狀態(tài)改為 SHUTDOWN 標(biāo)記
unlockRunState(rs, (rs & ~RSLOCK) | SHUTDOWN);
}
// 如果線程池還不是stop停止?fàn)顟B(tài)(rs&stop==1表示處于stop狀態(tài))
if ((rs & STOP) == 0) {
// 如果now入?yún)閒alse會(huì)進(jìn)入如下邏輯
if (!now) { // check quiescence
// 遍歷整個(gè)工作隊(duì)列數(shù)組
for (long oldSum = 0L;;) { // repeat until stable
WorkQueue[] ws; WorkQueue w; int m, b; long c;
// 以目前的ctl值作為初始效驗(yàn)和
long checkSum = ctl;
// 檢測(cè)池內(nèi)活躍線程數(shù),如果>0則不能直接置為stop狀態(tài)
if ((int)(checkSum >> AC_SHIFT) + (config & SMASK) > 0)
return false; // still active workers
// 如果工作隊(duì)列全部被注銷了則可以設(shè)置為stop狀態(tài)
if ((ws = workQueues) == null || (m = ws.length - 1) <= 0)
break; // check queues
// 開啟循環(huán)
for (int i = 0; i <= m; ++i) {
// 循環(huán)每個(gè)工作隊(duì)列
if ((w = ws[i]) != null) {
// 如果隊(duì)列中還存在任務(wù),且當(dāng)前隊(duì)列處于活躍狀態(tài)
if ((b = w.base) != w.top || w.scanState >= 0 ||
w.currentSteal != null) {
// 喚醒空閑的線程幫助執(zhí)行還未處理的任務(wù)
tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
return false; // arrange for recheck
}
// 以棧底作為校驗(yàn)和
checkSum += b;
// 將偶數(shù)位隊(duì)列中的任務(wù)全部取消(外部提交的任務(wù))
if ((i & 1) == 0)
w.qlock = -1; // try to disable external
}
}
// 循環(huán)數(shù)組兩次后,效驗(yàn)和都一致,代表任務(wù)都空了,
// 同時(shí)也沒(méi)有新的線程被創(chuàng)建出來(lái),那么可以設(shè)置stop狀態(tài)了
if (oldSum == (oldSum = checkSum))
break;
}
}
// 如果線程池還未stop,那么則設(shè)置為stop狀態(tài)
if ((runState & STOP) == 0) {
rs = lockRunState(); // enter STOP phase
unlockRunState(rs, (rs & ~RSLOCK) | STOP);
}
}
int pass = 0; // 3 passes to help terminate
for (long oldSum = 0L;;) { // or until done or stable
WorkQueue[] ws; WorkQueue w; ForkJoinWorkerThread wt; int m;
long checkSum = ctl;
// 所有隊(duì)列全部已經(jīng)空了或所有線程都注銷了
if ((short)(checkSum >>> TC_SHIFT) + (config & SMASK) <= 0 ||
(ws = workQueues) == null || (m = ws.length - 1) <= 0) {
// 如果線程池是還不是TERMINATED狀態(tài)
if ((runState & TERMINATED) == 0) {
rs = lockRunState(); // done
// 先將線程池狀態(tài)改為TERMINATED狀態(tài)
unlockRunState(rs, (rs & ~RSLOCK) | TERMINATED);
synchronized (this) { notifyAll(); } // for awaitTermination
}
break;
}
// 開啟循環(huán)
for (int i = 0; i <= m; ++i) {
// 處理每個(gè)隊(duì)列
if ((w = ws[i]) != null) {
checkSum += w.base;
w.qlock = -1; // try to disable
if (pass > 0) {
// 取消每個(gè)隊(duì)列中的所有任務(wù)
w.cancelAll(); // clear queue
// 中斷執(zhí)行線程,喚醒所有被掛起的線程
if (pass > 1 && (wt = w.owner) != null) {
if (!wt.isInterrupted()) {
try { // unblock join
wt.interrupt();
} catch (Throwable ignore) {
}
}
if (w.scanState < 0)
U.unpark(wt); // wake up
}
}
}
}
// 如果兩次效驗(yàn)和不一致,賦值上一次的效驗(yàn)和
if (checkSum != oldSum) { // unstable
oldSum = checkSum;
pass = 0;
}
// 線程池狀態(tài)穩(wěn)定了
// 所有任務(wù)被取消,執(zhí)行線程被中斷,掛起線程被喚醒中斷了
else if (pass > 3 && pass > m) // can't further help
break;
// 如果有線程因?yàn)槭Щ畋粧炱? else if (++pass > 1) { // try to dequeue
long c; int j = 0, sp; // bound attempts
// 根據(jù)ctl中記錄的阻塞鏈喚醒所有線程
while (j++ <= m && (sp = (int)(c = ctl)) != 0)
tryRelease(c, ws[sp & m], AC_UNIT);
}
}
return true;
}
線程池關(guān)閉的實(shí)現(xiàn)邏輯也比較簡(jiǎn)單,首先會(huì)將線程池標(biāo)記為SHUTDOWN
狀態(tài),然后根據(jù)情況進(jìn)行下一步處理,如果線程池中沒(méi)啥活躍線程了,同時(shí)任務(wù)也不多了,將狀態(tài)改為STOP
狀態(tài),在STOP
狀態(tài)中會(huì)處理四件事:
- ①將所有活躍的隊(duì)列狀態(tài)改為注銷狀態(tài),
w.qlock=-1
- ②取消整個(gè)線程池中所有還未執(zhí)行的任務(wù)
- ③喚醒所有因?yàn)槭Щ顠炱鹱枞木€程
- ④嘗試中斷所有執(zhí)行的活躍線程,喚醒scanState<0的線程,確保一些還沒(méi)來(lái)得及掛起的線程也能被中斷
最后當(dāng)所有線程都被中斷了,并且未執(zhí)行的任務(wù)都被取消了,那么會(huì)把狀態(tài)改為TERMINATED
狀態(tài),線程池關(guān)閉完成。
五、總結(jié)
ForkJoin分支合并框架幾乎是整個(gè)JUC包源碼中最難的部分,因?yàn)檎麄€(gè)框架比較龐大,分析起來(lái)也比較復(fù)雜,到目前為止還剩下ManagedBlocker
與CoutedCompleter
沒(méi)有分析。因?yàn)閷?duì)于ForkJoin框架的分析篇幅比較長(zhǎng)了,所以對(duì)于這兩就不再進(jìn)行贅述,不過(guò)對(duì)CoutedCompleter
比較感興趣的可以參考一下:《CoutedCompleter分析》這篇文章,它的作用更多的是為Java8的Stream并行流提供服務(wù)。而ManagedBlocker
則是為ForkJoin框架提供處理阻塞型任務(wù)的支持。
總的來(lái)說(shuō),F(xiàn)orkJoin分支合并框架思想非常優(yōu)秀,完全的落地了分治以及工作竊取思想,整個(gè)框架中的各個(gè)成員各司其職卻有配合緊密,內(nèi)部采用了一個(gè)隊(duì)列數(shù)組以奇/偶位存儲(chǔ)內(nèi)外任務(wù),雙端隊(duì)列的方式實(shí)現(xiàn)工作與竊取思想。但是其內(nèi)部實(shí)現(xiàn)涉及了很多的位運(yùn)算知識(shí),所以半道出家以及工作多年的小伙伴會(huì)有些生疏,看其源碼實(shí)現(xiàn)會(huì)有些吃勁,但理解大體思想即可,對(duì)于任何源碼分析類的知識(shí)都無(wú)需拘泥其細(xì)節(jié)過(guò)程。
最后的總結(jié):
- 創(chuàng)建池ForkJoinPool,初始化并行數(shù)=cpu邏輯核心數(shù),池中沒(méi)有隊(duì)列,沒(méi)有線程
- 外部向線程池提交一個(gè)任務(wù):
pool.submit(task)
- 初始化隊(duì)列數(shù)組,容量:
2 * Max { 并行數(shù), 2 ^ n }
- 創(chuàng)建一個(gè)共享隊(duì)列,容量為2^13,隨機(jī)放在隊(duì)列數(shù)組的某一個(gè)偶數(shù)索引位
- 外部提交的任務(wù)存入這個(gè)共享隊(duì)列,位置值為2^12處
- 再創(chuàng)建一條線程,并為其分配一個(gè)隊(duì)列,容量為2^13,隨機(jī)放在數(shù)組中某個(gè)奇數(shù)索引位
- 線程啟動(dòng)執(zhí)行
- 隨機(jī)一個(gè)位置,線程從此位置開始遍歷所有隊(duì)列,最終掃描到前面提交的任務(wù),并將其從所在的隊(duì)列取出
- 線程執(zhí)行處理任務(wù),首先拆分出兩個(gè)子任務(wù)
- 如果用invokeAll提交,一個(gè)子任務(wù)執(zhí)行,另一個(gè)壓入隊(duì)列
- 如果用fork提交,則兩個(gè)都?jí)喝牍ぷ麝?duì)列
- 提交的子任務(wù)觸發(fā)創(chuàng)建新的線程并分配新的工作隊(duì)列,同樣放在奇數(shù)位置
- 提交的子任務(wù)可能仍然被當(dāng)前線程執(zhí)行,但也有可能被其它線程竊取
- 線程在子任務(wù)處join合并,join期間會(huì)幫助竊取者處理任務(wù),竊取它的任務(wù)執(zhí)行
- 優(yōu)先偷竊取者隊(duì)列棧底的任務(wù)
- 如果竊取者隊(duì)列為空,則會(huì)根據(jù)竊取鏈去找竊取者的竊取者偷任務(wù).....
- 如果整個(gè)池內(nèi)都沒(méi)任務(wù)了,則進(jìn)入阻塞,阻塞前會(huì)根據(jù)情況補(bǔ)償活躍線程
- 提交的子任務(wù)不管被哪條線程執(zhí)行,仍可能會(huì)重復(fù)上述拆分/提交/竊取/阻塞步驟
- 當(dāng)任務(wù)被拆分的足夠細(xì),達(dá)到了拆分閾值時(shí),才會(huì)真正的開始執(zhí)行這些子任務(wù)
- 處理完成會(huì)和拆分任務(wù)時(shí)一樣,遞歸一層一層返回結(jié)果
- 直至最終所有子任務(wù)全部都執(zhí)行結(jié)束,從而合并所有子結(jié)果,得到最終結(jié)果
- 如果外部沒(méi)有再提交任務(wù),所有線程掃描不到會(huì)被滅活,會(huì)進(jìn)入失活(inactive)狀態(tài)
- 一直沒(méi)有任務(wù)時(shí),線程池會(huì)削減線程數(shù),直至最終所有線程銷毀,所有奇數(shù)索引位的隊(duì)列被注銷,F(xiàn)orkJoinPool中只剩下一個(gè)最初創(chuàng)建的在偶數(shù)索引位的隊(duì)列,以便于再次接受外部提交的任務(wù),然后再?gòu)念^開始重復(fù)所有步驟....