- Fork/Join 框架是 JDK 1.7 提供的並行執行任務框架,這個框架通過(遞歸)把問題劃分爲子任務,然後並行的執行這些子任務,等所有的子任務都結束的時候,再合併最終結果的這種方式來支持並行計算編程。
- 總體的設計參考了爲 Cilk 設計的 work-stealing 框架。
- Fork/Join 並行方式是獲取良好的並行計算性能的一種最簡單同時也是最有效的設計技術,是 分治算法(Divide-and-Conquer) 的並行版本。
- Fork 操作啓動一個新的並行 Fork/Join 子任務。
- Join 操作一直等待直到所有的子任務都結束。
- Fork/Join 算法,如同其他分治算法一樣,總是會遞歸的、反覆的劃分子任務,直到這些子任務可以用足夠簡單的、短小的順序方法來執行。
fork/join框架是ExecutorService
接口的一種具體實現,目的是爲了幫助你更好地利用多處理器帶來的好處。它是爲那些能夠被遞歸地拆解成子任務的工作類型量身設計的。其目的在於能夠使用所有可用的運算能力來提升你的應用的性能。
類似於ExecutorService
接口的其他實現,fork/join框架會將任務分發給線程池中的工作線程。fork/join框架的獨特之處在與它使用工作竊取(work-stealing)算法。完成自己的工作而處於空閒的工作線程能夠從其他仍然處於忙碌(busy)狀態的工作線程處竊取等待執行的任務。
fork/join框架的核心是ForkJoinPool
類,它是對AbstractExecutorService
類的擴展。ForkJoinPool
實現了工作偷取算法,並可以執行ForkJoinTask
任務。
基本使用方法
使用fork/join框架的第一步是編寫執行一部分工作的代碼。你的代碼結構看起來應該與下面所示的僞代碼類似:
if (當前這個任務工作量足夠小)
直接完成這個任務
else
將這個任務或這部分工作分解成兩個部分
分別觸發(invoke)這兩個子任務的執行,並等待結果
你需要將這段代碼包裹在一個ForkJoinTask
的子類中。不過,通常情況下會使用一種更爲具體的的類型,或者是RecursiveTask
(會返回一個結果),或者是RecursiveAction
。
當你的ForkJoinTask
子類準備好了,創建一個代表所有需要完成工作的對象,然後將其作爲參數傳遞給一個ForkJoinPool
實例的invoke()
方法即可。
2. 工作竊取(work-stealing)
- ForkJoin 框架的核心在於輕量級調度機制,使用了 工作竊取(Work-Stealing)所採用的基本調度策略。
work-stealing
- 每一個工作線程維護自己的調度隊列中的可運行任務。
- 隊列以雙端隊列的形式被維護。
- 支持後進先出 LIFO 的 push 和 pop 操作。
- 支持先進先出 FIFO 的 take 操作。
- 對於一個給定的工作線程來說,任務所產生的子任務將會被放入到工作者自己的雙端隊列中。
- 工作線程使用後進先出 LIFO(最新的元素優先)的順序,通過彈出任務來處理隊列中的任務。
- 當一個工作線程的本地沒有任務去運行的時候,它將使用先進先出 FIFO 的規則嘗試隨機的從別的工作線程中拿(竊取)一個任務去運行。
- 當一個工作線程觸及了 Join 操作,如果可能的話它將處理其他任務,直到目標任務被告知已經結束(通過
isDone()
方法)。所有的任務都會 無阻塞 的完成。 - 當一個工作線程無法再從其他線程中獲取任務和失敗處理的時候,它就會退出並經過一段時間之後再度嘗試直到所有的工作線程都被告知他們都處於空閒的狀態。
- 在這種情況下,他們都會阻塞直到其他的任務再度被上層調用。
- 使用後進先出 LIFO 用來處理每個工作線程的自己任務,但是使用先進先出 FIFO 規則用於獲取別的任務,這是一種被廣泛使用的進行遞歸 Fork/Join 設計的一種調優手段。
- 讓竊取任務的線程從隊列擁有者相反的方向進行操作會減少線程競爭。
- 同樣體現了遞歸分治算法的大任務優先策略。
- 更早期被竊取的任務有可能會提供一個更大的單元任務,從而使得竊取線程能夠在將來進行遞歸分解。
- 對於一些基礎的操作而言,使用相對較小粒度的任務比那些僅僅使用粗粒度劃分的任務以及那些沒有使用遞歸分解的任務的運行速度要快。儘管相關的少數任務在大多數的 Fork/Join 框架中會被其他工作線程竊取,但是創建許多組織良好的任務意味着只要有一個工作線程處於可運行的狀態,那麼這個任務就有可能被執行。
工作竊取算法的優點
- 利用了線程進行並行計算,減少了線程間的競爭。
工作竊取算法的缺點
- 如果雙端隊列中只有一個任務時,線程間會存在競爭。
- 竊取算法消耗了更多的系統資源,如會創建多個線程和多個雙端隊列。
3. ForkJoinPool
- ForkJoinPool 類是 Fork/Join 框架 的核心,和 ThreadPoolExecutor 一樣是 ExecutorService 接口的實現類。
- ForkJoinPool 不是爲了替代 ExecutorService,而是它的補充,在某些應用場景下性能比 ExecutorService 更好。(見 Java Tip: When to use ForkJoinPool vs ExecutorService )
ForkJoinPool 類圖
- ForkJoinPool 的兩大核心就是 分而治之(Divide-and-Conquer)和工作竊取(Work-Stealing)算法。
- ForkJoinPool 最適合的是計算密集型的任務,如果存在 I/O,線程間同步,
sleep()
等會造成線程長時間阻塞的情況時,最好配合使用 ManagedBlocker。
- ForkJoinPool 最適合的是計算密集型的任務,如果存在 I/O,線程間同步,
3.1 Fork/Join 框架的使用
- ThreadPoolExecutor 中每個任務都是由單個線程獨立處理的,如果出現一個非常耗時的大任務(比如大數組排序),就可能出現線程池中只有一個線程在處理這個大任務,而其他線程卻空閒着,這會導致 CPU 負載不均衡,空閒的處理器無法幫助工作繁忙的處理器。
- ForkJoinPool 可以用來解決這種問題,將一個大任務拆分成多個小任務後,使用 Fork 可以將小任務分發給其他線程同時處理,使用 Join 可以將多個線程處理的結果進行彙總。
問題
- 計算 1 至 1000 的正整數之和。
解決方法
- 通過 ExecutorService 實現。
public class ExecutorServiceCalculator implements Calculator {
private int parallism;
private ExecutorService pool;
public ExecutorServiceCalculator() {
parallism = Runtime.getRuntime().availableProcessors(); // CPU的核心數
pool = Executors.newFixedThreadPool(parallism);
}
private static class SumTask implements Callable<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
public Long call() throws Exception {
long total = 0;
for (int i = from; i <= to; i++) {
total += numbers[i];
}
return total;
}
}
@Override
public long sumUp(long[] numbers) {
List<Future<Long>> results = new ArrayList<>();
// 把任務分解爲 n 份,交給 n 個線程處理
int part = numbers.length / parallism;
for (int i = 0; i < parallism; i++) {
int from = i * part;
int to = (i == parallism - 1) ? numbers.length - 1 : (i + 1) * part - 1;
results.add(pool.submit(new SumTask(numbers, from, to)));
}
// 把每個線程的結果相加,得到最終結果
long total = 0L;
for (Future<Long> f : results) {
try {
total += f.get();
} catch (Exception ignore) {}
}
return total;
}
}
- 通過 ForkJoinPool 實現。
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() {
// 當需要計算的數字小於6時,直接計算結果
if (to - from < 6) {
long total = 0;
for (int i = from; i <= to; i++) {
total += numbers[i];
}
return total;
// 否則,把任務一分爲二,遞歸計算
} 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));
}
}
- 使用了 ForkJoinPool 的實現邏輯全部集中在
compute()
函數中,代碼中沒有顯式地把任務分配給線程,只是分解了任務,而把具體的任務到線程的映射都交給了 ForkJoinPool 來完成。
3.2 Fork/Join 框架的原理
ForkJoinPool
- ForkJoinPool 的每個工作線程都維護着一個工作隊列(WorkQueue),這是一個雙端隊列(Deque),裏面存放的對象是任務(ForkJoinTask)。
- 每個工作線程在運行中產生新的任務(通常是因爲調用了
fork()
)時,會放入工作隊列的隊尾,並且工作線程在處理自己的工作隊列時,使用的是 LIFO 方式,也就是說每次從隊尾取出任務來執行。 - 每個工作線程在處理自己的工作隊列同時,會嘗試竊取一個任務(或是來自於剛剛提交到 pool 的任務,或是來自於其他工作線程的工作隊列),竊取的任務位於其他線程的工作隊列的隊首,也就是說工作線程在竊取其他工作線程的任務時,使用的是 FIFO 方式。
- 在遇到
join()
時,如果需要 Join 的任務尚未完成,則會先處理其他任務,並等待其完成。 - 在既沒有自己的任務,也沒有可以竊取的任務時,進入休眠。
- 每個工作線程在運行中產生新的任務(通常是因爲調用了
ForkJoinPool
- ForkJoinPool 是用於執行 ForkJoinTask 任務的執行池,不再是傳統執行池 Worker+Queue 的組合模式,而是維護了一個隊列數組 WorkQueue(WorkQueue[]),這樣在提交任務和線程任務的時候大幅度的減少碰撞。
WorkQueue
- WorkQueue 是雙向列表,用於任務的有序執行,如果 WorkQueue 用於自己的執行線程 Thread,線程默認將會從尾端選取任務用來執行 LIFO。
- 每個 ForkJoinWorkThread 都有屬於自己的 WorkQueue,但不是每個 WorkQueue 都有對應的 ForkJoinWorkThread。
- 沒有 ForkJoinWorkThread 的 WorkQueue 保存的是 submission,來自外部提交,在 WorkQueue[] 的下標是 偶數 位。
ForkJoinWorkThread
- ForkJoinWorkThread 是用於執行任務的線程,用於區別使用非 ForkJoinWorkThread 線程提交的 task。啓動一個該 Thread,會自動註冊一個 WorkQueue 到 Pool,擁有 Thread 的 WorkQueue 只能出現在 WorkQueue[] 的 奇數 位。
work-stealing
ForkJoinTask
- ForkJoinTask 是任務,它比傳統的任務更加輕量,不再是 Runnable 的子類,提供 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;
}
join 方法
join()
的工作則複雜得多,也是它可以使得線程免於被阻塞的原因。- 檢查調用
join()
的線程是否是 ForkJoinThread 線程。如果不是(例如 main 線程),則阻塞當前線程,等待任務完成。如果是,則不阻塞。 - 查看任務的完成狀態,如果已經完成,直接返回結果。
- 如果任務尚未完成,但處於自己的工作隊列內,則完成它。
- 如果任務已經被其他的工作線程偷走,則竊取這個小偷的工作隊列內的任務(以 FIFO 方式)執行,以期幫助它早日完成預 join 的任務。
- 如果偷走任務的小偷也已經把自己的任務全部做完,正在等待需要 Join 的任務時,則找到小偷的小偷,幫助它完成它的任務。
- 遞歸地執行第 5 步。
- 檢查調用
join 方法的流程
- 除了每個工作線程自己擁有的工作隊列以外,ForkJoinPool 自身也擁有工作隊列,這些工作隊列的作用是用來接收由外部線程(非 ForkJoinThread 線程)提交(submit)過來的任務,而這些工作隊列被稱爲 submitting queue。
submit()
和fork()
其實沒有本質區別,只是提交對象變成了 submitting queue 而已(還有一些同步,初始化的操作)。submitting queue 和其他 work queue 一樣,是工作線程 " 竊取 " 的對象,因此當其中的任務被一個工作線程成功竊取時,就意味着提交的任務真正開始進入執行階段。
- ForkJoinPool 有一個 Async Mode ,效果是工作線程在處理本地任務時也使用 FIFO 順序。這種模式下的 ForkJoinPool 更接近於是一個消息隊列,而不是用來處理遞歸式的任務。
- 在需要阻塞工作線程時,可以使用 ManagedBlocker。
- JDK 1.8 新增加的 CompletableFuture 類可以實現類似於 Javascript 的 promise-chain,內部就是使用 ForkJoinPool 來實現的。
3.2.1 ForkJoinPool
- 作爲框架的提交入口,ForkJoinPool 管理着線程池中線程和任務隊列,標識線程池是否還接收任務,顯示現在的線程運行狀態。
// 低位和高位掩碼
private static final long SP_MASK = 0xffffffffL;
private static final long UC_MASK = ~SP_MASK;
// 活躍線程數
private static final int AC_SHIFT = 48;
private static final long AC_UNIT = 0x0001L << AC_SHIFT; //活躍線程數增量
private static final long AC_MASK = 0xffffL << AC_SHIFT; //活躍線程數掩碼
// 工作線程數
private static final int TC_SHIFT = 32;
private static final long TC_UNIT = 0x0001L << TC_SHIFT; //工作線程數增量
private static final long TC_MASK = 0xffffL << TC_SHIFT; //掩碼
private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15); // 創建工作線程標誌
// 池狀態
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;
// 實例字段
volatile long ctl; // 主控制參數
volatile int runState; // 運行狀態鎖
final int config; // 並行度|模式
int indexSeed; // 用於生成工作線程索引
volatile WorkQueue[] workQueues; // 主對象註冊信息,workQueue
final ForkJoinWorkerThreadFactory factory;// 線程工廠
final UncaughtExceptionHandler ueh; // 每個工作線程的異常信息
final String workerNamePrefix; // 用於創建工作線程的名稱
volatile AtomicLong stealCounter; // 偷取任務總數,也可作爲同步監視器
/** 靜態初始化字段 */
//線程工廠
public static final ForkJoinWorkerThreadFactory defaultForkJoinWorkerThreadFactory;
//啓動或殺死線程的方法調用者的權限
private static final RuntimePermission modifyThreadPermission;
// 公共靜態pool
static final ForkJoinPool common;
//並行度,對應內部common池
static final int commonParallelism;
//備用線程數,在tryCompensate中使用
private static int commonMaxSpares;
//創建workerNamePrefix(工作線程名稱前綴)時的序號
private static int poolNumberSequence;
//線程阻塞等待新的任務的超時值(以納秒爲單位),默認2秒
private static final long IDLE_TIMEOUT = 2000L * 1000L * 1000L; // 2sec
//空閒超時時間,防止timer未命中
private static final long TIMEOUT_SLOP = 20L * 1000L * 1000L; // 20ms
//默認備用線程數
private static final int DEFAULT_COMMON_MAX_SPARES = 256;
//阻塞前自旋的次數,用在在awaitRunStateLock和awaitWork中
private static final int SPINS = 0;
//indexSeed的增量
private static final int SEED_INCREMENT = 0x9e3779b9;
ForkJoinPool 對象
- 使用 Executors 工具類創建。
// parallelism 定義並行級別
public static ExecutorService newWorkStealingPool(int parallelism);
// 默認並行級別爲 JVM 可用的處理器個數
// Runtime.getRuntime().availableProcessors()
public static ExecutorService newWorkStealingPool();
- 使用 ForkJoinPool 內部已經初始化好的 commonPool 方法創建。
// 類靜態代碼塊中會調用makeCommonPool方法初始化一個commonPool
public static ForkJoinPool commonPool() {
// assert common != null : "static init error";
return common;
}
- common 在 static{} 裏創建,調用的是
makeCommonPool()
,最終調用 ForkJoinPool 的構造函數。
private ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
int mode,
String workerNamePrefix) {
this.workerNamePrefix = workerNamePrefix;
this.factory = factory;
this.ueh = handler;
this.config = (parallelism & SMASK) | mode;
long np = (long)(-parallelism); // offset ctl counts
this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
}
參數說明
類型及其修飾符 | 變量名 | 作用 |
---|---|---|
volatile long | ctl | 主控制參數,分爲 4 個區域保存(每 16 位爲 1 個區域)。 |
volatile int | runState | 保存線程池的 運行狀態。 |
final int | config | 保存線程池的 最大線程數量 及其 是否採用了公平模式。 |
int | indexSeed | 用於在構造 WorkQueue 時計算插入到 workQueues 的下標。 |
volatile WorkQueue[] | workQueues | 線程池持有的工作線程(即執行任務的線程)。 |
final ForkJoinWorkerThreadFactory | factory | 該線程池指定的線程工廠,用於生產 ForkJoinWorkerThread 對象。 |
final String | workerNamePrefix | 該線程池中工作線程的名稱前綴。 |
volatile AtomicLong | stealCounter | 該線程池中所有的 WorkQueue 總共被竊取的任務數量。 |
ctl 變量說明
- ctl 是 ForkJoinPool 中最主控制參數字段,按 16 位 爲一組封裝在一個 long 中,共 64 位。
- ctl 變量區域如下表(表 1)所示。
區域 | 屬性 | 說明 |
---|---|---|
1 | AC | 正在運行工作線程數減去目標並行度,高 16 位。(49-64 位) |
2 | TC | 總工作線程數減去目標並行度,中高 16 位。(33-48 位) |
3 | SS | 棧頂等待線程的版本計數和狀態,中低 16 位。(17-32 位) |
4 | ID | 棧頂 WorkQueue 在池中的索引(poolIndex),低 16 位。(1-16 位) |
- ctl 的低 32 位其實只能表示一個等待(空閒)線程,低 32 位標識的是棧頂的那個,能從棧頂中的變量 stackPred 追蹤到下一個等待(空閒)線程。
- AC 和 TC 初始化時取的是 parallelism 負數,後續代碼可以直接判斷正負,爲負代表還沒有達到目標數量(閾值)。
- 可以通過 ctl 的低 32 位
sp=(int)ctl
來檢查工作線程狀態。當 sp 爲 0 時說明此刻 沒有已經啓動但是空閒的線程。 - 當 ctl<0 意味着 active 的線程還沒有到達閾值。
線程池狀態(runState )說明
// 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。使用 bit 位來標識不同狀態。
- runState 記錄了 ForkJoinPool 的運行狀態,除了 SHUTDOWN 是負數,其他都是正數。
- 多線程環境修改 runState,需要先獲取鎖,RSLOCK 和 RSIGNAL 在這裏被使用。
private int lockRunState() {
int rs;
return ((((rs = runState) & RSLOCK) != 0 ||
!U.compareAndSwapInt(this, RUNSTATE, rs, rs |= RSLOCK)) ?
awaitRunStateLock() : rs);
}
- 如果執行 runState & RSLOCK ==0 就直接說明目前的運行狀態沒有被 鎖住,其他情況類似。
config 變量
- config 負責保存最大的線程數量以及公平模式,config 爲 int 類型(32 位),前 16 位(低 16 位)保存最大線程數量,第 17 位(高 16 位)爲 1 時,代表公平模式(FIFO_QUEUE),爲 0 時代表非公平模式(LIFO_QUEUE)。
static final int SMASK = 0xffff; // short bits == max index
static final int LIFO_QUEUE = 0;
static final int FIFO_QUEUE = 1 << 16;
this.config = (parallelism & SMASK) | mode;
WorkQueue 對象
- WorkQueue 是一個雙端隊列,定義在 ForkJoinPool 類裏。
WorkQueue
類型及其修飾符 | 變量名 | 作用 |
---|---|---|
volatile int | scanState | 保存這個 WorkQueue 的類型,線程是否繁忙(僅限 ACTIVE 類型)。 |
int | stackPred | 記錄前驅 worker 的下標。 |
int | nsteals | 該 WorkQueue 被竊取的任務的總數。 |
int | hint | 用於竊取線程計算下次竊取的 workQueues 數組的下標。 |
int | config | 前 16 位(低 16 位)保存該 WorkQueue 在 workQueues 數組的下標,第 17 位(高 16 位)保存屬於 LIFO 還是 FIFO 模式。 |
volatile int | qlock | 一個簡單的鎖,0 表示爲加鎖,1 表示已加鎖,小於 0 表示當前 WorkQueue 已停止。 |
ForkJoinTask<?>[] | array | 任務隊列,保存 ForkJoinTask 任務對象。 |
volatile int | base | bash 與 workQueues 數組長度取模的值竊取線程下次從 workQueues 數組取出任務的下標。 |
int | top | top 與 workQueues 數組長度取模的值即爲下次將任務對象插入到 workQueues 數組的下標。 |
final ForkJoinPool | pool | 該 WorkQueue 對應的線程池。 |
final ForkJoinWorkerThread | owner | 該 WorkQueue 對應的工作線程對象(ACTIVE 類型的 WorkQueue 不會爲 null)。 |
volatile Thread | parker | 當 currentThread 被 park(等待)時,用來保存這個線程對象來後續 unpark。 |
ForkJoinTask<?> | currentJoin | 調用 join 方法時等待結果的任務對象。 |
ForkJoinTask<?> | currentSteal | 保存正在執行的從別的 WorkQueue 竊取過來的任務。 |
WorkQueue 當前狀態(scanState)
static final int SCANNING = 1; // false when running tasks
static final int INACTIVE = 1 << 31; // must be negative
static final int SS_SEQ = 1 << 16;
- scanState 描述 WorkQueue 當前狀態。
- 高 16 位中,最高位表示 WorkQueue 屬於 ACTIVE 還是 INACTIVE 類型,當最高位爲 0 時表示 ACTIVE 類型,爲 1 時表示 INACTIVE 類型。
- 還保存了這個 WorkQueue 對應的線程是否正在執行任務(僅 ACTIVE 類型)。
- 低 16 位中,保存了這個 WorkQueue 在數組 workQueues 中的下標。
WorkQueue 鎖標識(qlock)
- 操作 WorkQueue 前需要鎖定,記錄在字段 qlock。
- 1 鎖定。
- 0 未鎖定。
- 負數,對應的 worker 已經撤銷註冊,WorkQueue 終止使用。
config
- WorkQueue 的 config 記錄了在 WorkQueue[] 的下標和當前 mode。
- 如果有自己的 owner 默認是 LIFO。
- 下面代碼中的 config 是 ForkJoinPool 的 config,與 WorkQueue 的 w.config 不是相同變量。
static final int MODE_MASK = 0xffff << 16; // top half of int
int mode = config & MODE_MASK;
w.config = i | mode;
top 和 base
- base 是 work-steal 的偏移量,因爲其他的線程都可以 竊取 該隊列的任務,所以 base 使用 volatile 標識。
- 因爲只有 owner 的 Thread 才能從 top 端取任務,所以在設置變量時,
int top
不需要使用 volatile。 - WorkQueue 的任務隊列是一個數組實現的雙端隊列,其 top%workQueues.length 的值代表新任務插入到 workQueues 數組的位置,bash%workQueues.length 的值代表竊取線程從 workQueues 數組取出任務的下標。
- 在構造 WorkQueue 時,base 和 top 的值相等並且值爲 4096(默認 array 數組的長度爲 8192),當 array 數組容量不夠時,會調用 growArray 進行擴容,擴容後的數組長度爲原先的 2 倍大小。
static final int INITIAL_QUEUE_CAPACITY = 1 << 13;
base = top = INITIAL_QUEUE_CAPACITY >>> 1;
stackPred
- stackPred 用於維護一個存儲空閒線程的棧(也可看成是一個單向鏈表),該變量的值代表前面一個空閒線程在 workQueues 數組的下標。其棧頂的空閒線程下標存儲在 ForkJoinPool 的變量 ctl 中低16 位,第 17 位保存該空閒線程是 ACTIVE 類型還是 INACTIVE 類型。