前言
ForkJoinPool
作爲線程池,ForkJoinTask
爲任務,ForkJoinWorkerThread
作爲執行任務的線程,三者構成的任務調度機制在 Java 中通常被稱爲ForkJoin 框架
ForkJoin
框架在 Java 7 的時候就加入到了 Java 併發包 java.util.concurrent
,並且在 Java 8 的 lambda 並行流
中充當着底層框架的角色。其主要實現的功能是採用“分而治之”的算法將一個大型複雜任務 fork()分解
成足夠小的任務才使用多線程去並行處理這些小任務,處理完得到各個小任務的執行結果後再進行join()合併
,將其彙集成一個大任務的結果,最終得到最初提交的那個大型複雜任務的執行結果
ForkJoin框架
適用於非循環依賴的純函數的計算或孤立對象的操作,如果存在 I/O,線程間同步,sleep() 等會造成線程長時間阻塞的情況時會因爲任務依賴影響整體效率,此時可配合使用 ManagedBlocker
。ForkJoinPool
線程池爲了提高任務的並行度和吞吐量做了很多複雜的設計實現,目的是充分利用CPU,其中最著名的就是 work stealing
任務竊取機制
ManagedBlocker
相當於明確告訴ForkJoinPool
有個任務要阻塞了,ForkJoinPool
就會啓用另一個線程來運行任務,以最大化地利用CPU
1. ForkJoinPool 的組件
-
ForkJoin
框架各組件關係如下所示。ForkJoinPool
作爲最核心的組件,維護了所有任務隊列的數組workQueues。這個數組的結構與 HashMap 底層數組一致,大小爲 2 的次冪,每個工作線程用ThreadLocalRandom.getProbe()
作爲 hash 值,經過計算即可得出工作線程私有的任務隊列WorkQueue
在任務隊列數組workQueues
中的數組下標另外需注意,
WorkQueue
任務隊列其實也分爲了兩種類型,一種是Submission Queue
,也就是外部提交進來的任務所佔用的隊列,其在任務隊列數組中的數組下標爲偶數;另一種是Work Queue
,屬於工作線程私有的任務隊列,保存大任務 fork 分解出來的任務,其在任務隊列數組中的數組下標爲奇數 -
Work Queue
類型的WorkQueue
對象通過變量pool
持有ForkJoinPool線程池
引用,用來獲取其 workQueues 任務隊列數組,從而竊取其他工作線程的任務來執行。WorkQueue
中的ForkJoinTask<?>[] array
數組保存了每一個具體的任務,數組的大小也爲 2 的次冪,這個數據是爲了高效定位數組中的元素。其初始化大小爲 8192,任務在數組中的下標初始化爲 4096,插入數組中的第一個任務通常是處理量最大的任務。WorkQueue
對象中的owner
指向其所屬的ForkJoinWorkerThread
工作線程,這樣ForkJoinPool
就可以通過WorkQueue
來操作工作線程 -
ForkJoinWorkerThread
工作線程啓動後就會掃描偷取任務執行,另外當其在 ForkJoinTask#join() 等待返回結果時如果被 ForkJoinPool 線程池發現其任務隊列爲空或者已經將當前任務執行完畢,也會通過工作竊取算法從其他任務隊列中獲取任務分配到其任務隊列中並執行
1.1 線程池 ForkJoinPool
ForkJoinPool
的繼承體系如下,可以看到它和 ThreadPoolExecutor
一樣都是繼承自AbstractExecutorService
抽象類,所以它和 ThreadPoolExecutor
的使用幾乎沒什麼區別,只是任務對象變成了 ForkJoinTask
1.1.1 ForkJoinPool 線程池的創建
與其他線程池類似,ForkJoinPool
線程池可使用以下方式創建
Executors.newWorkStealingPool()
public static ExecutorService newWorkStealingPool(int parallelism) { return new ForkJoinPool (parallelism, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); }
構造方法中各個參數的含義如下:
parallelism
: 並行度
即配置線程池線程個數,如果沒有指定,則默認爲Runtime.getRuntime().availableProcessors() - 1
,最大值不能超過MAX_CAP =32767
,可通過以下方式自定義配置System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "8"); // 或者啓動參數指定 -Djava.util.concurrent.ForkJoinPool.common.parallelism=8
factory
: 工作線程ForkJoinWorkerThread
的創建工廠handler
: 未捕獲異常的處理器類
多線程中子線程拋出的異常是無法被主線程 catch 的,因此可能需要使用UncaughtExceptionHandler
對異常進行統一處理asyncMode
: 任務隊列處理任務模式
默認爲false
,使用 LIFO(後入先出,類似棧結構)策略處理任務;爲true
時處理策略是 FIFO(先進先出),更接近於一個消息隊列,不適用於處理遞歸式的任務
1.1.2 ForkJoinPool 線程池內部重要屬性
// 配合ctl在控制線程數量時使用
private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15);
// 控制ForkJoinPool創建線程數量,(ctl & ADD_WORKER) != 0L 時創建線程,也就是當ctl的第16位不爲0時,可以繼續創建線程
volatile long ctl; // main pool control
// 全局鎖控制,全局運行狀態
volatile int runState; // lockable status
// 低16位表示並行數量,高16位表示 ForkJoinPool 處理任務的模式(異步/同步)
final int config; // parallelism, mode
// 工作任務隊列數組
volatile WorkQueue[] workQueues; // main registry
// 默認線程工廠,默認實現是DefaultForkJoinWorkerThreadFactory
final ForkJoinWorkerThreadFactory factory;
1.2 工作線程 ForkJoinWorkerThread
ForkJoinWorkerThread
的繼承體系如下, 它繼承了 Thread
類,主要在 Thread
的基礎上新增了變量,並重寫run()
方法
1.2.1 ForkJoinWorkerThread 的創建
與其他線程池類似,ForkJoinWorkerThread
線程由線程池創建,創建後立即調用start()
方法啓動。 ForkJoinWorkerThread
創建時會向ForkJoinPool線程池
註冊,此時線程池會將其設置爲守護線程,併爲其創建 ForkJoinPool.WorkQueue
隊列後將引用返回,之後ForkJoinWorkerThread
就可以從 workQueue
裏面取任務出來處理了
-
ForkJoinPool#createWorker()
private boolean createWorker() { ForkJoinWorkerThreadFactory fac = factory; Throwable ex = null; ForkJoinWorkerThread wt = null; try { if (fac != null && (wt = fac.newThread(this)) != null) { wt.start(); return true; } } catch (Throwable rex) { ex = rex; } deregisterWorker(wt, ex); return false; }
-
ForkJoinWorkerThread#ForkJoinWorkerThread()
protected ForkJoinWorkerThread(ForkJoinPool pool) { // Use a placeholder until a useful name can be set in registerWorker super("aForkJoinWorkerThread"); this.pool = pool; this.workQueue = pool.registerWorker(this); }
1.2.2 ForkJoinWorkerThread 重要屬性
// 工作線程所屬線程池
final ForkJoinPool pool; // the pool this thread works in
// 私有的任務隊列,在 ForkJoinWorkerThread 新建的時候由線程池爲其創建
final ForkJoinPool.WorkQueue workQueue; // work-stealing mechanics
1.3 線程任務 ForkJoinTask
ForkJoinTask
與FutureTask
一樣實現了Future
接口,不過它是一個抽象類,使用時通常不會直接實現ForkJoinTask
,而是實現其三個抽象子類。ForkJoinTask
僅僅用於配合ForkJoinPool
實現任務的調度執行,實現其抽象子類則側重於提供任務的拆分與執行邏輯
RecursiveAction
用於大多數不返回結果的計算RecursiveTask
用於返回結果的計算CountedCompleter
用於那些操作完成之後觸發其他操作的操作,提供了外部回調方法, Java 8 新增,在 Stream 並行流中使用極多。CountedCompleter
在創建實例的時候還可以傳入一個CountedCompleter
實例,因此可以形成樹狀的任務結構,樹上的所有任務是可以並行執行的,且每一個子任務完成後都可以通過tryComplete()
輔助其父任務的完成
1.3.1 ForkJoinTask 重要屬性
status
字段將運行控制狀態位打包到單個 int
中,以最小化佔用空間並通過CAS確保原子性,其高16位存儲任務執行狀態例如 NORMAL,CANCELLED 或 EXCEPTIONAL
,低16位預留用於用戶自定義的標記
- 任務未完成之前
status
大於等於0,完成之後就是NORMAL、CANCELLED 或 EXCEPTIONAL
這幾個小於 0 的值,這幾個值也有大小順序:0(初始狀態) > NORMAL > CANCELLED > EXCEPTIONAL
/*
* The status field holds run control status bits packed into a
* single int to minimize footprint and to ensure atomicity (via
* CAS). Status is initially zero, and takes on nonnegative
* values until completed, upon which status (anded with
* DONE_MASK) holds value NORMAL, CANCELLED, or EXCEPTIONAL. Tasks
* undergoing blocking waits by other threads have the SIGNAL bit
* set. Completion of a stolen task with SIGNAL set awakens any
* waiters via notifyAll. Even though suboptimal for some
* purposes, we use basic builtin wait/notify to take advantage of
* "monitor inflation" in JVMs that we would otherwise need to
* emulate to avoid adding further per-task bookkeeping overhead.
* We want these monitors to be "fat", i.e., not use biasing or
* thin-lock techniques, so use some odd coding idioms that tend
* to avoid them, mainly by arranging that every synchronized
* block performs a wait, notifyAll or both.
*
* These control bits occupy only (some of) the upper half (16
* bits) of status field. The lower bits are used for user-defined
* tags.
*/
/** 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
2. 任務處理流程
2.1 任務的提交
2.1.1 通過 ForkJoinPool 的外部提交
2.1.1.1 外部任務提交的方法
ForkJoinPool
主要提供了以下三個方法來完成任務的提交。這三個方法在處理流程上並無明顯差別,主要差異體現在其方法返回值上
方法 | 特性 |
---|---|
invoke() |
返回任務執行的結果 |
execute() |
無返回值 |
submit() |
返回 ForkJoinTask 對象,可以通過該對象取消任務/獲取結果 |
2.1.1.2 外部任務提交的流程
-
以
ForkJoinPool#invoke()
方法爲觸發起點,可以看到其首先調用externalPush(task)
方法將任務提交, 然後調用task.join()
等待獲取任務執行結果public <T> T invoke(ForkJoinTask<T> task) { if (task == null) throw new NullPointerException(); externalPush(task); return task.join(); }
-
ForkJoinPool#externalPush()
首先需要判斷線程池中的任務隊列數組workQueues
以及當前線程所對應的Submission Queue是否已經創建,如果已經創建了,則將任務入隊;如果沒有創建,則調用externalSubmit(task)
final void externalPush(ForkJoinTask<?> task) { WorkQueue[] ws; WorkQueue q; int m; int r = ThreadLocalRandom.getProbe(); int rs = runState; if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 && (q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) { ForkJoinTask<?>[] a; int am, n, s; 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); U.putOrderedInt(q, QTOP, s + 1); U.putIntVolatile(q, QLOCK, 0); if (n <= 1) signalWork(ws, q); return; } U.compareAndSwapInt(q, QLOCK, 1, 0); } externalSubmit(task); }
-
ForkJoinPool#externalSubmit()
內部是一個空循環,跳出邏輯主要結合runState
來控制,內部工作分爲3步,每個步驟都使用了鎖的機制來處理併發事件,既有對runState
使用ForkJoinPool
的全局鎖,也有對WorkQueue
使用局部鎖- 完成任務隊列數組 workQueues的初始化,數組長度爲大於等於2倍並行數量的且是2的n次冪的數,然後扭轉 runState
- 計算出一個workQueues數組偶數下標,如該位置沒有任務隊列,新建一個
Submission Queue
隊列,並將其添加到 workQueues 數組對應下標。隊列中的ForkJoinTask<?>[] array
數組初始化大小爲 8192,雙端指針base/top
初始化爲 4096(top指針用來表示任務正常出隊入隊,每當有任務入隊 top +1,每當有任務出列 top -1。base指針用來表示任務偷取狀況,每當任務被偷取 base +1。當 base 小於top時,說明有數據可以取) - 計算出的workQueues數組偶數下標位置已經有任務隊列,將任務 CAS 入隊,任務隊列 top +1,標記任務爲已提交,並調用
signalWork()
,跳出空循環
private void externalSubmit(ForkJoinTask<?> task) { int r; // initialize caller's probe if ((r = ThreadLocalRandom.getProbe()) == 0) { ThreadLocalRandom.localInit(); r = ThreadLocalRandom.getProbe(); } for (;;) { WorkQueue[] ws; WorkQueue q; int rs, m, k; boolean move = false; if ((rs = runState) < 0) { tryTerminate(false, false); // help terminate throw new RejectedExecutionException(); } else if ((rs & STARTED) == 0 || // initialize ((ws = workQueues) == null || (m = ws.length - 1) < 0)) { int ns = 0; rs = lockRunState(); try { if ((rs & STARTED) == 0) { U.compareAndSwapObject(this, STEALCOUNTER, null, new AtomicLong()); // create workQueues array with size a power of two int p = config & SMASK; // ensure at least 2 slots int n = (p > 1) ? p - 1 : 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1; workQueues = new WorkQueue[n]; ns = STARTED; } } finally { unlockRunState(rs, (rs & ~RSLOCK) | ns); } } 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; // initial submission or resizing try { // locked version of push if ((a != null && a.length > s + 1 - q.base) || (a = q.growArray()) != null) { int j = (((a.length - 1) & s) << ASHIFT) + ABASE; U.putOrderedObject(a, j, task); U.putOrderedInt(q, QTOP, s + 1); submitted = true; } } finally { U.compareAndSwapInt(q, QLOCK, 1, 0); } if (submitted) { signalWork(ws, q); return; } } move = true; // move on failure } else if (((rs = runState) & RSLOCK) == 0) { // create new queue q = new WorkQueue(this, null); q.hint = r; q.config = k | SHARED_QUEUE; q.scanState = INACTIVE; rs = lockRunState(); // publish index if (rs > 0 && (ws = workQueues) != null && k < ws.length && ws[k] == null) ws[k] = q; // else terminated unlockRunState(rs, rs & ~RSLOCK); } else move = true; // move if busy if (move) r = ThreadLocalRandom.advanceProbe(r); } }
-
ForkJoinPool#signalWork()
會根據ctl
的值判斷是否需要創建工作線程,當前工作線程太少會調用tryAddWorker()
嘗試創建新的工作線程;其他情況如果有工作線程在休眠,會U.unpark(p)
將其喚醒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; } }
-
ForkJoinPool#tryAddWorker()
會一直嘗試調用createWorker()
創建工作線程,直到控制工作線程數量的相關標記達到目標值private void tryAddWorker(long c) { boolean add = false; do { long nc = ((AC_MASK & (c + AC_UNIT)) | (TC_MASK & (c + TC_UNIT))); if (ctl == c) { int rs, stop; // check if terminating if ((stop = (rs = lockRunState()) & STOP) == 0) add = U.compareAndSwapLong(this, CTL, c, nc); unlockRunState(rs, rs & ~RSLOCK); if (stop != 0) break; if (add) { createWorker(); break; } } } while (((c = ctl) & ADD_WORKER) != 0L && (int)c == 0); }
-
ForkJoinPool#createWorker()
主要負責通過線程工廠創建工作線程,並調用其start()
方法啓動線程private boolean createWorker() { ForkJoinWorkerThreadFactory fac = factory; Throwable ex = null; ForkJoinWorkerThread wt = null; try { if (fac != null && (wt = fac.newThread(this)) != null) { wt.start(); return true; } } catch (Throwable rex) { ex = rex; } deregisterWorker(wt, ex); return false; }
-
線程工廠最終調用到
ForkJoinWorkerThread#ForkJoinWorkerThread()
來創建一個工作線程,並將其向ForkJoinPool線程池
註冊,之後線程池會爲其創建私有的Work Queue
任務隊列後將引用返回protected ForkJoinWorkerThread(ForkJoinPool pool) { // Use a placeholder until a useful name can be set in registerWorker super("aForkJoinWorkerThread"); this.pool = pool; this.workQueue = pool.registerWorker(this); }
-
ForkJoinPool#registerWorker()
將工作線程設置爲守護線程,並將新建的Work Queue
任務隊列放入任務隊列數組奇數下標中。至此任務隊列數組workQueus
中已經有了兩種任務隊列Submission Queue
和Work Queue
final WorkQueue registerWorker(ForkJoinWorkerThread wt) { UncaughtExceptionHandler handler; wt.setDaemon(true); // configure thread if ((handler = ueh) != null) wt.setUncaughtExceptionHandler(handler); WorkQueue w = new WorkQueue(this, wt); int i = 0; // assign a pool index int mode = config & MODE_MASK; int rs = lockRunState(); try { WorkQueue[] ws; int n; // skip if no array if ((ws = workQueues) != null && (n = ws.length) > 0) { int s = indexSeed += SEED_INCREMENT; // unlikely to collide int m = n - 1; i = ((s << 1) | 1) & m; // odd-numbered indices if (ws[i] != null) { // collision int probes = 0; // step by approx half n int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2; while (ws[i = (i + step) & m] != null) { if (++probes >= n) { workQueues = ws = Arrays.copyOf(ws, n <<= 1); m = n - 1; probes = 0; } } } w.hint = s; // use as random seed w.config = i | mode; w.scanState = i; // publication fence ws[i] = w; } } finally { unlockRunState(rs, rs & ~RSLOCK); } wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1))); return w; }
2.1.2 通過 ForkJoinTask 的內部提交
ForkJoinTask#fork()
提交,返回 ForkJoinTask對象,通過該對象可以對任務進行控制,比如獲取、取消等操作。該方法首先判斷當前線程是否是 ForkJoinWorkerThread
線程,如果是說明是內部大任務分解的小任務,直接將任務插入其私有隊列,如果不是說明是外部提交,需要走外部任務提交流程
- 入隊時將新任務始終 push 到隊列一端的方式可以保證較大的任務在隊列的頭部,越小的任務越在尾部。這時候擁有該任務隊列的工作線程如果按照
LIFO(棧結構)
的方式彈出任務執行,將會優先從小任務開始,逐漸往大任務進行,而竊取任務的其他工作線程從隊列頭部(FIFO方式
)開始竊取將會幫助它完成大任務
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;
}
2.2 任務的處理
2.2.1 ForkJoinWorkerThread 的任務處理循環
-
ForkJoinWorkerThread
工作線程啓動後,其run()
方法就會被調用。方法中首先是判斷當前工作線程的workQueue.array
是否爲null
,毫無疑問,這個判斷只在工作線程創建的時候成立一次,之後就會調用線程池的runWorker()
方法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); } } } }
-
ForkJoinPool#runWorker()
開始初始化WorkQueue
工作隊列中的array
數組,並開啓了空循環一直調用 scan() 方法掃描偷取任務,獲取到任務後即執行final void runWorker(WorkQueue w) { // 1. 初始化或者兩倍擴容 array 數組 w.growArray(); // allocate queue int seed = w.hint; // initially holds randomization hint int r = (seed == 0) ? 1 : seed; // avoid 0 for xorShift // 2. 空循環 for (ForkJoinTask<?> t;;) { // 3. scan()方法掃描偷取任務,working stealing 算法 if ((t = scan(w, r)) != null) // 4. 偷取到任務即執行 w.runTask(t); // 5. 未偷取到任務則 await 等待 else if (!awaitWork(w, r)) break; r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift } }
-
ForkJoinPool#scan()
爲work stealing
算法的重要實現,此處暫不分析,接下去看偷取到任務後的執行邏輯WorkQueue#runTask()
,該方法的主要邏輯先調用(currentSteal = task).doExec()
方法執行偷取到的任務,再調用execLocalTasks()
方法執行當前任務隊列中的任務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(); } }
-
ForkJoinTask#doExec()
方法首先判斷任務狀態是否大於等於 0 (大於等於 0 說明任務還沒有執行完成),因爲這個任務可能被其它線程竊取過去處理完了。方法內主要是執行exec()
方法,再根據方法返回值設置任務的完成狀態final int doExec() { int s; boolean completed; if ((s = status) >= 0) { try { completed = exec(); } catch (Throwable rex) { return setExceptionalCompletion(rex); } if (completed) s = setCompletion(NORMAL); } return s; }
-
ForkJoinTask#exec()
是抽象方法,主要由子類重寫。以CountedCompleter#exec()
方法爲例,其內部其實就是調用compute()
方法,而這個方法正是CountedCompleter
子類重寫實現,內部主要爲任務分解與計算的邏輯。任務分解
會調用ForkJoinTask#fork()
方法,根據當前線程實例決定將其提交到線程池Submission Queue
任務隊列還是工作線程Work Queue
私有任務隊列protected final boolean exec() { compute(); return false; }
2.2.2 join() 觸發的處理流程
ForkJoinTask#join()
會等待任務執行完成,並返回執行結果。若任務被取消拋出CancellationException
異常,若是其他異常導致異常結束則拋出相關RuntimeException
或Error
信息,這些異常可能包括由於內部資源耗盡而導致的RejectedExecutionException
,比如分配內部任務隊列失敗
-
ForkJoinTask#join()
方法的內部邏輯很簡單,就是調用doJoin()
方法,然後根據任務的狀態決定是否要reportException()
報告異常,如果任務正常結束,就調用getRawResult()
返回執行結果public final V join() { int s; if ((s = doJoin() & DONE_MASK) != NORMAL) reportException(s); return getRawResult(); }
-
ForkJoinTask#doJoin()
方法內部邏輯很複雜,主要分爲了以下幾步:- doJoin() 首先判斷staus是不是已完成,如果完成了(status < 0)就直接返回,因爲這個任務可能被其它線程竊取過去處理掉了
- 其次判斷當前線程是否是 ForkJoinWorkerThread:
- 是的話直接嘗試
tryUnpush(this)
出隊然後doExec()
執行任務處理。如果沒有出隊成功並且處理成功,則執行wt.pool.awaitJoin(w, this, 0L)
,等待任務執行完成 - 不是的話執行
externalAwaitDone()
等待外部任務執行完成
- 是的話直接嘗試
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 : //調用pool的執行邏輯,並等待返回執行結果狀態 wt.pool.awaitJoin(w, this, 0L) : //調用pool的等待機制 externalAwaitDone(); }
-
wt.pool.awaitJoin(w, this, 0L)
執行了線程池的方法ForkJoinPool#awaitJoin()
,其內部邏輯如下- 更新 WorkQueue 的 currentJoin
- 空循環開啓,如果任務已經結束((s = task.status) < 0),則 break
- 如果任務爲 CountedCompleter 類型,則調用
helpComplete()
幫助父任務完成 - 隊列爲空或者任務已經執行成功(可能被其他線程偷取),則幫助偷取了自己任務的工作線程執行任務(互相幫助)
helpStealer()
- 如果任務已經結束((s = task.status) < 0),則 break
- 如果超時結束,則 break
- 執行失敗的情況下,執行補償操作
tryCompensate()
- 當前任務完成後,替換 currentJoin 爲以前的值
final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) { int s = 0; if (task != null && w != null) { ForkJoinTask<?> prevJoin = w.currentJoin; U.putOrderedObject(w, QCURRENTJOIN, task); CountedCompleter<?> cc = (task instanceof CountedCompleter) ? (CountedCompleter<?>)task : null; for (;;) { if ((s = task.status) < 0) break; if (cc != null) helpComplete(w, cc, 0); else if (w.base == w.top || w.tryRemoveAndExec(task)) helpStealer(w, task); 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; if (tryCompensate(w)) { task.internalWait(ms); U.getAndAddLong(this, CTL, AC_UNIT); } } U.putOrderedObject(w, QCURRENTJOIN, prevJoin); } return s; }
-
ForkJoinTask#externalAwaitDone()
的處理邏輯也比較簡單,步驟如下:1.首先判斷當前任務是否是 CountedCompleter,是的話調用線程池
externalHelpComplete()
嘗試將其入Submission Queue 隊列
,由某個工作線程偷取幫助執行;否則調用ForkJoinPool.common.tryExternalUnpush(this) ? doExec()
嘗試把自己出隊然後執行掉
2. 如果任務沒有執行成功,利用 object/wait 的方法去監聽任務 status 的狀態變更private int externalAwaitDone() { int s = ((this instanceof CountedCompleter) ? // try helping ForkJoinPool.common.externalHelpComplete( (CountedCompleter<?>)this, 0) : ForkJoinPool.common.tryExternalUnpush(this) ? doExec() : 0); 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(0L); } catch (InterruptedException ie) { interrupted = true; } } else notifyAll(); } } } while ((s = status) >= 0); if (interrupted) Thread.currentThread().interrupt(); } return s; }
3. work stealing 算法
work stealing 算法
的實現主要體現在 ForkJoinPool#scan()
方法,其主要機制如下:
ForkJoinPool
的每個工作線程都維護着一個私有的雙端任務隊列Work Queue
,每個工作線程在運行中產生的新任務(通常是因爲調用了fork()
),會放入工作隊列的隊尾,並且工作線程在處理自己的工作隊列時,使用的是LIFO 方式
,其實就是棧結構- 每個工作線程在處理自己的工作隊列同時,會嘗試竊取一個任務(剛剛提交到線程池的
Submission Queue
隊列中的任務,或是來自於其他工作線程的工作隊列),竊取的任務位於其他線程的工作隊列的隊首,也就是使用的 FIFO 方式,目的是減少競爭 - 在遇到
join()
時,如果需要join
的任務尚未完成,則會先處理其他任務,並等待其完成 - 在既沒有自己的任務,也沒有可以竊取的任務時,進入休眠
ForkJoinPool
除了工作竊取機制
,還有一種互助機制
,體現在ForkJoinPool#helpStealer()
方法 。假設工作線程2竊取了工作線程1的任務之後,通過fork
又分解產生了子任務,這些子任務會進入工作線程2的工作隊列。這時候如果工作線程1把剩餘的任務都完成了,當它發現自己的任務被工作線程2竊取了,那它也會試着去竊取工作線程2的任務 (你偷了我的,我有空就要偷你的),這就是互助機制
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;
}