一、前言
Fork/Join框架是Java 1.7之后引入的基于分治算法的并行框架,官網(wǎng)文檔是這么介紹的:
- Fork/Join框架是ExecutorService接口的一種具體實(shí)現(xiàn)算灸,可以更好的幫助您利用多個(gè)處理器蹈胡;它是為那些可以遞歸地分割成小塊的工作而設(shè)計(jì)的决采,該框架的目標(biāo)是使用所有可用的處理能力來(lái)提高應(yīng)用程序的性能域滥。
- 與任何ExecutorService實(shí)現(xiàn)一樣,F(xiàn)ork/Join框架也會(huì)將任務(wù)分發(fā)給線程池中的工作線程去執(zhí)行序矩,F(xiàn)ork/Join框架的獨(dú)特之處在于它使用了一種工作竊取算法(work-stealing)鸯绿,也就是說(shuō)完成自己的工作而處于空閑的工作線程能夠從其他處于忙碌(busy)狀態(tài)的工作線程處竊取等待執(zhí)行的任務(wù)。
- Fork/Join框架的核心類是ForkJoinPool贮泞,F(xiàn)orkJoinPool實(shí)現(xiàn)了工作偷取算法楞慈,并可以執(zhí)行ForkJoinTask任務(wù),得到計(jì)算結(jié)果啃擦。
本文所使用的JDK版本是 JDK 8.0囊蓝。
二、Fork/Join框架介紹
1. Fork/Join簡(jiǎn)介
??Fork/Join的并行算法的原理是分治算法令蛉,通俗的來(lái)講就是聚霜,將大任務(wù)分割成足夠小的小任務(wù),然后讓線程池中不同的線程來(lái)執(zhí)行這些分割出來(lái)的小任務(wù)珠叔,小任務(wù)完成之后再將小任務(wù)的結(jié)果合并成大任務(wù)的結(jié)果蝎宇。也就是fork子任務(wù),join返回結(jié)果祷安。典型的用法如下:
Result solve(Problem problem) {
if (problem is small) {
directly solve problem
} else {
split problem into independent parts
fork new subtasks to solve each part
join all subtasks
compose result from subresults
}
}
2. Fork/Join框架中的類介紹
Fork/Join框架的幾個(gè)核心的類如下:
最核心的類是
ForkJoinPool
姥芥,該類接受的任務(wù)對(duì)象是ForkJoinTask
,F(xiàn)orkJoinTask是一個(gè)抽象類汇鞭,它有兩個(gè)常用的子類:RecursiveTask
(有返回值)和RecursiveAction
(無(wú)返回值)凉唐,一般情況下庸追,我們不需要直接使用ForkJoinTask,而是通過(guò)繼承它的兩個(gè)子類台囱,并實(shí)現(xiàn)對(duì)應(yīng)的抽象方法 ——compute
來(lái)定義我們的任務(wù)淡溯。
3. ForkJoinPool類
我們先來(lái)看下ForkJoinPool這個(gè)類,來(lái)看下這個(gè)類的構(gòu)造方法及常用的方法簿训。
3.1 ForkJoinPool構(gòu)造方法
ForkJoinPool的構(gòu)造方法共有3個(gè)咱娶,但最終都會(huì)調(diào)用同一個(gè)構(gòu)造方法。
public ForkJoinPool() {
this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
defaultForkJoinWorkerThreadFactory, null, false);
}
public ForkJoinPool(int parallelism) {
this(parallelism, defaultForkJoinWorkerThreadFactory, null, false);
}
public ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
boolean asyncMode) {
this(checkParallelism(parallelism),
checkFactory(factory),
handler,
asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
"ForkJoinPool-" + nextPoolId() + "-worker-");
checkPermission();
}
我們來(lái)看一下構(gòu)造方法中涉及到的參數(shù):
- parallelism强品,并行度膘侮,也可以說(shuō)是工作線程數(shù)量,默認(rèn)是系統(tǒng)可用處理器的數(shù)量择懂,也就是邏輯CPU的個(gè)數(shù)喻喳,最小是1;
- ForkJoinWorkerThreadFactory困曙,創(chuàng)建工作線程的工廠表伦,工作線程的對(duì)象是
ForkJoinWorkerThread
;- UncaughtExceptionHandler慷丽,處理工作線程發(fā)生異常的異常處理類蹦哼,默認(rèn)是null;
- asyncMode要糊,同步或異步模式纲熏,如果是true的話,那么在處理任務(wù)時(shí)工作線程的模式為FIFO 順序锄俄,這種模式下的ForkJoinPool更像是一個(gè)隊(duì)列的形式局劲,并且任務(wù)不能被合并,默認(rèn)是false奶赠;
3.2 ForkJoinPool公共池
??在很多情況下鱼填,如果沒有特殊的應(yīng)用需求,我們一般可以直接使用ForkJoinPool中的common池毅戈。ForkJoinPool提供了一種公共池苹丸,可以用來(lái)處理那些沒有被顯式提交到任何線程池的任務(wù),并且可以通過(guò)指定系統(tǒng)參數(shù)的方式定義“并行度苇经、線程工廠和異常處理類”赘理,并且它指定了mode模式為LIFO_QUEUE
,也就是說(shuō)可以支持任務(wù)合并(join)扇单,來(lái)看一下它的主要代碼:
private static ForkJoinPool makeCommonPool() {
int parallelism = -1;
ForkJoinWorkerThreadFactory factory = null;
UncaughtExceptionHandler handler = null;
try { // ignore exceptions in accessing/parsing properties
String pp = System.getProperty
("java.util.concurrent.ForkJoinPool.common.parallelism");
String fp = System.getProperty
("java.util.concurrent.ForkJoinPool.common.threadFactory");
String hp = System.getProperty
("java.util.concurrent.ForkJoinPool.common.exceptionHandler");
if (pp != null)
parallelism = Integer.parseInt(pp);
if (fp != null)
factory = ((ForkJoinWorkerThreadFactory)ClassLoader.
getSystemClassLoader().loadClass(fp).newInstance());
if (hp != null)
handler = ((UncaughtExceptionHandler)ClassLoader.
getSystemClassLoader().loadClass(hp).newInstance());
} catch (Exception ignore) {
}
if (factory == null) {
if (System.getSecurityManager() == null)
factory = defaultForkJoinWorkerThreadFactory;
else // use security-managed default
factory = new InnocuousForkJoinWorkerThreadFactory();
}
if (parallelism < 0 && // default 1 less than #cores
(parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0)
parallelism = 1;
if (parallelism > MAX_CAP)
parallelism = MAX_CAP;
return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE,
"ForkJoinPool.commonPool-worker-");
}
首先商模,支持我們定義系統(tǒng)參數(shù)parallelism
,threadFactory
,exceptionHandler
阻桅,其次凉倚,該方法最后也調(diào)用了ForkJoinPool的構(gòu)造方法,并且指定了mode模式為LIFO_QUEUE
嫂沉。
當(dāng)然,F(xiàn)orkJoinPool提供了commonPool
方法可以直接獲取公共池:
public static ForkJoinPool commonPool() {
// assert common != null : "static init error";
return common;
}
3.3 執(zhí)行ForkJoinTask
使用ForkJoinPool 扮碧,我們有三個(gè)方法來(lái)執(zhí)行ForkJoinTask任務(wù):invoke方法趟章,submit方法,execute方法慎王。
public <T> T invoke(ForkJoinTask<T> task)
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task)
public void execute(ForkJoinTask<?> task)
- invoke方法蚓土,用來(lái)執(zhí)行有返回值的任務(wù),并且該方法是阻塞的赖淤,直到任務(wù)執(zhí)行完畢蜀漆,該方法才會(huì)停止阻塞并返回任務(wù)的執(zhí)行結(jié)果;
- execute方法咱旱,用來(lái)執(zhí)行沒有返回值的任務(wù)确丢,該方法同樣是阻塞的,并且除了從Executor接口中繼承的execute方法外吐限,F(xiàn)orkJoinPool 也定義了用來(lái)執(zhí)行ForkJoinTask 的 execute方法鲜侥;
- submit方法,用來(lái)執(zhí)行有返回值的任務(wù)诸典,該方法是非阻塞的描函,調(diào)用之后將任務(wù)提交給 ForkJoinPool 去執(zhí)行便立即返回,返回已經(jīng)提交到ForkJoinPool去執(zhí)行的task狐粱,同樣該方法除了從ExecutorService 接口繼承的submit方法外舀寓,也重載了用來(lái)執(zhí)行ForkJoinTask的方法;
4. 工作竊取算法
我們這里來(lái)簡(jiǎn)單介紹下work-stealing算法的基本調(diào)度策略:
- 線程池中的每一個(gè)工作線程維護(hù)自己的調(diào)度隊(duì)列中的可運(yùn)行任務(wù)肌蜻;
- 隊(duì)列是一個(gè)雙端隊(duì)列互墓,既支持后進(jìn)先出(LIFO的push和pop操作),還支持先進(jìn)先出 (FIFO的take操作)宋欺;
- 對(duì)于一個(gè)給定的工作線程來(lái)說(shuō)轰豆,任務(wù)所產(chǎn)生的子任務(wù)將會(huì)被放入到工作者自己的雙端隊(duì)列中;
- 工作線程使用后進(jìn)先出 (LIFO齿诞,最新的元素優(yōu)先) 的順序酸休,通過(guò)彈出任務(wù)來(lái)處理隊(duì)列中的任務(wù);
- 當(dāng)一個(gè)工作線程的本地沒有任務(wù)去運(yùn)行的時(shí)候祷杈,它將使用先進(jìn)先出(FIFO)的規(guī)則嘗試隨機(jī)的從別的工作線程中拿(『竊取』)一個(gè)任務(wù)去運(yùn)行斑司;
- 當(dāng)一個(gè)工作線程觸及了join操作時(shí),如果需要join的任務(wù)尚未完成,那會(huì)先處理其他任務(wù)宿刮,直到目標(biāo)任務(wù)被告知已經(jīng)結(jié)束(通過(guò)isDone方法)互站;
- 當(dāng)一個(gè)工作線程無(wú)任務(wù)可執(zhí)行,并且無(wú)任務(wù)可竊取或者這中間發(fā)生了異常僵缺,獲取任務(wù)和失敗處理的時(shí)候胡桃,它就會(huì)退出(通過(guò)yield、sleep或者優(yōu)先級(jí)調(diào)整)并經(jīng)過(guò)一段時(shí)間之后再度嘗試直到所有的工作線程都被告知他們都處于空閑的狀態(tài)磕潮。在這種情況下翠胰,他們都會(huì)阻塞直到其他的任務(wù)再度被上層調(diào)用;
??使用后進(jìn)先出 (LIFO) 用來(lái)處理每個(gè)工作線程的自己任務(wù)自脯,但是使用先進(jìn)先出 (FIFO) 規(guī)則用于獲取別的任務(wù)之景,這是一種被廣泛使用的進(jìn)行遞歸Fork/Join設(shè)計(jì)的一種調(diào)優(yōu)手段。讓竊取任務(wù)的線程從隊(duì)列擁有者相反的方向進(jìn)行操作會(huì)減少線程競(jìng)爭(zhēng)膏潮,同樣體現(xiàn)了遞歸分治算法的大任務(wù)優(yōu)先策略锻狗。
5. ForkJoinTask類
??我們?cè)賮?lái)簡(jiǎn)單說(shuō)下ForkJoinTask類。前面我們也說(shuō)過(guò)焕参,該抽象類繼承自ForkJoinTask
接口轻纪,所以它可以有返回值。Fork/Join框架最主要的兩個(gè)流程就是fork流程和join流程龟糕,所以我們主要來(lái)看下 ForkJoinTask 的fork方法
和join方法
桐磁。
5.1 fork方法
fork方法用于將大任務(wù)拆分為小任務(wù),然后執(zhí)行小任務(wù)讲岁,來(lái)簡(jiǎn)單看下代碼:
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)前線程我擂,然后判斷線程類型是否是ForkJoinPool中的工作線程;
- 如果是缓艳,說(shuō)明是fork分割的子任務(wù)校摩,然后將任務(wù)添加到這個(gè)線程對(duì)應(yīng)的任務(wù)隊(duì)列中,等待被執(zhí)行阶淘;
- 如果當(dāng)前線程不是ForkJoinWorkerThread類型的線程衙吩,那么就會(huì)將該任務(wù)提交到公共池的隨機(jī)的隊(duì)列中去;
5.2 join方法
join方法會(huì)獲取所有子任務(wù)的執(zhí)行結(jié)果溪窒,然后遞歸的合并結(jié)果:
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
這里源碼就部多說(shuō)了坤塞,有點(diǎn)復(fù)雜,只簡(jiǎn)單說(shuō)下大致的流程:
- 獲取當(dāng)前線程澈蚌,然后判斷線程類型是否是ForkJoinPool中的工作線程摹芙;
- 如果不是,阻塞當(dāng)前線程(awaitJoin)宛瞄,等待任務(wù)執(zhí)行完成浮禾;
- 如果是,檢查任務(wù)的執(zhí)行狀態(tài),如果任務(wù)已經(jīng)完成直接返回結(jié)果盈电;如果沒有完成蝴簇,并且在自己的任務(wù)隊(duì)列內(nèi),則執(zhí)行該任務(wù)匆帚;
- 而如果任務(wù)被其他工作線程竊取熬词,則竊取這個(gè)偷取者隊(duì)列內(nèi)的任務(wù)(FIFO),然后幫助這個(gè)竊取者執(zhí)行它的任務(wù)吸重〉磁欤基本思想是:偷取者幫助我執(zhí)行任務(wù),我去幫助偷取者執(zhí)行它的任務(wù)晤锹;
- 在幫助偷取者執(zhí)行任務(wù)后,如果調(diào)用者發(fā)現(xiàn)自己隊(duì)列已經(jīng)有任務(wù)彤委,則依次彈出自己的任務(wù)(LIFO)并執(zhí)行鞭铆;
- 如果竊取者已經(jīng)把自己的任務(wù)做完,正在等待著需要join的任務(wù)時(shí)焦影,則找到偷取者的偷取者车遂,幫助它完成它的任務(wù);
- 循環(huán)執(zhí)行上面的操作斯辰;
子任務(wù)執(zhí)行完的結(jié)果會(huì)統(tǒng)一放在一個(gè)隊(duì)列里舶担,然后啟動(dòng)一個(gè)線程從隊(duì)列里拿數(shù)據(jù),最后合并這些數(shù)據(jù)彬呻。
至于源碼的解讀衣陶,可詳細(xì)參考地址:JUC源碼分析-線程池篇(五):ForkJoinPool - 2,博主分析的特別詳細(xì)闸氮。
6. 代碼示例
下面通過(guò)兩個(gè)簡(jiǎn)單的例子來(lái)看一下ForkJoinPool和ForkJoinTask的使用剪况。
6.1 RecursiveAction無(wú)返回值
第一個(gè)例子來(lái)看一下RecursiveAction的使用,無(wú)返回值蒲跨,打印一些隨機(jī)數(shù):
package task;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;
/**
* 打印數(shù)值译断,無(wú)返回值
*/
class RecursiveActionTest extends RecursiveAction {
/**
* 閾值,每個(gè)"小任務(wù)"最多只打印5個(gè)數(shù)
*/
private static final int MAX = 5;
private int start;
private int end;
private RecursiveActionTest(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
// 當(dāng)end-start的值小于MAX時(shí)候或悲,開始打印
if ((end - start) < MAX) {
for (int i = start; i < end; i++) {
System.out.println(Thread.currentThread().getName() + "的i值:" + i);
}
} else {
// 將大任務(wù)分解成兩個(gè)小任務(wù)
int middle = (start + end) / 2;
RecursiveActionTest left = new RecursiveActionTest(start, middle);
RecursiveActionTest right = new RecursiveActionTest(middle, end);
// 并行執(zhí)行兩個(gè)小任務(wù)
left.fork();
right.fork();
}
}
public static void main(String[] args) throws Exception {
// 默認(rèn)線程數(shù) 邏輯CPU的個(gè)數(shù)
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 提交可分解的PrintTask任務(wù)
forkJoinPool.submit(new RecursiveActionTest(0, 20));
// 阻塞當(dāng)前線程直到 ForkJoinPool 中所有的任務(wù)都執(zhí)行結(jié)束
forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);
// 關(guān)閉線程池
forkJoinPool.shutdown();
}
}
打印結(jié)果:
ForkJoinPool-1-worker-2的i值:5
ForkJoinPool-1-worker-3的i值:13
ForkJoinPool-1-worker-1的i值:0
ForkJoinPool-1-worker-0的i值:3
ForkJoinPool-1-worker-1的i值:1
ForkJoinPool-1-worker-3的i值:14
ForkJoinPool-1-worker-2的i值:6
這里只列舉了部分值孙咪,可以看到,F(xiàn)orkJoinPool啟動(dòng)了4個(gè)線程來(lái)執(zhí)行這個(gè)任務(wù)巡语,因?yàn)槲译娔X的邏輯CPU個(gè)數(shù)是4個(gè)翎蹈,并且可以看到分解后的任務(wù)是并行執(zhí)行的,并不是順序執(zhí)行的捌臊。
6.1 RecursiveTask有返回值
下面來(lái)看一下RecursiveTask杨蛋,計(jì)算一個(gè)整數(shù)數(shù)組的和:
package task;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* 計(jì)算一個(gè)大的整數(shù)數(shù)組的和
*
* @author zhangkeke
* @since 2018/9/27 15:09
*/
public class RecursiveTaskTest extends RecursiveTask<Integer> {
/**
* 閾值,每個(gè)小任務(wù)處理的數(shù)量
*/
private static final int THRESHOLD = 5;
private int[] array;
private int low;
private int high;
private RecursiveTaskTest(int[] array, int low, int high) {
this.array = array;
this.low = low;
this.high = high;
}
@Override
protected Integer compute() {
int sum = 0;
if (high - low <= THRESHOLD) {
// 小于閾值則直接計(jì)算
for (int i = low; i < high; i++) {
sum += array[i];
}
} else {
// 一個(gè)大任務(wù)分割成兩個(gè)子任務(wù)
int mid = (low + high) >>> 1;
RecursiveTaskTest left = new RecursiveTaskTest(array, low, mid);
RecursiveTaskTest right = new RecursiveTaskTest(array, mid + 1, high);
// 異步執(zhí)行
left.fork();
right.fork();
// 以上兩行也可以使用 invokeAll(left,right);
// invokeAll方法會(huì)執(zhí)行很多任務(wù),并且會(huì)阻塞逞力,直到這些任務(wù)都執(zhí)行完成
// 結(jié)果合并
sum = left.join() + right.join();
}
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
int[] array = new int[20];
for (int i = 0; i < array.length; i++) {
array[i] = new Random().nextInt(100);
}
System.out.println(Arrays.toString(array));
// 開始創(chuàng)建任務(wù)
RecursiveTaskTest sumTask = new RecursiveTaskTest(array, 0, array.length - 1);
// 創(chuàng)建ForkJoinPool線程池
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 提交任務(wù)到線程池
forkJoinPool.submit(sumTask);
// 獲取結(jié)果曙寡,get方法會(huì)阻塞
Integer result = sumTask.get();
System.out.println("計(jì)算結(jié)果:" + result);
}
}
計(jì)算結(jié)果為:
[38, 98, 45, 49, 76, 74, 43, 18, 11, 73, 56, 0, 91, 7, 80, 2, 85, 88, 96, 87]
計(jì)算結(jié)果:801
該部分代碼來(lái)自:https://blog.csdn.net/ouyang_peng/article/details/46491217
6.3 fork方法問(wèn)題
在網(wǎng)上看到有人說(shuō)在進(jìn)行計(jì)算的時(shí)候,fork方法有點(diǎn)問(wèn)題寇荧,代碼是:
left.fork();
right.fork();
應(yīng)該使用invokeAll的方式举庶,這樣可以避免線程的浪費(fèi),實(shí)現(xiàn)線程的重用:
invokeAll(left, right);
記不清這是哪里的問(wèn)題了揩抡,有時(shí)間再研究下這塊户侥。
三、總結(jié)
??到這里基本上就大致介紹完了Fork/Join框架的流程了峦嗤,不過(guò)我們需要注意下該框架的適用場(chǎng)景蕊唐,其實(shí)也就是分治算法的適用場(chǎng)景,畢竟Fork/Join框架也可以看作是分治算法的并發(fā)版本了烁设。
- Fork/Join框架的適用場(chǎng)景替梨,簡(jiǎn)單來(lái)說(shuō)就是密集型計(jì)算,也就是我們的任務(wù)可以拆分成足夠小的任務(wù)装黑,并且可用根據(jù)小任務(wù)的結(jié)果來(lái)組裝大任務(wù)的結(jié)果副瀑;比如求一個(gè)大數(shù)組中的最大值/最小值,求和恋谭,排序等類似操作糠睡。
- 需要注意的是,選取劃分子任務(wù)的粒度(分割任務(wù)的閾值疚颊,也就是臨界值)對(duì)ForkJoinPool執(zhí)行任務(wù)的效率有很大影響狈孔,使用Fork/Join框架并不一定比順序執(zhí)行任務(wù)的效率高。
- 如果閾值選取過(guò)大串稀,任務(wù)分割的不夠細(xì)除抛,則不能充分利用CPU資源;
- 閾值太小母截,則可能會(huì)產(chǎn)生過(guò)多的子任務(wù)到忽,那么子任務(wù)的調(diào)度開銷可能會(huì)大于并行計(jì)算的性能開銷,并且我們還需要考慮創(chuàng)建子任務(wù)清寇、fork()子任務(wù)喘漏、線程調(diào)度以及合并子任務(wù)處理結(jié)果的耗時(shí)以及相應(yīng)的內(nèi)存消耗;
- 官方文檔給出的粗略經(jīng)驗(yàn)是:任務(wù)應(yīng)該執(zhí)行100~10000個(gè)基本的計(jì)算步驟华烟。決定子任務(wù)的粒度的最好辦法是實(shí)踐翩迈,通過(guò)實(shí)際測(cè)試結(jié)果來(lái)確定這個(gè)閾值才是最明智的做法。
再簡(jiǎn)單說(shuō)下 ForkJoinPool 和 ThreadPoolExecutor 的區(qū)別:
- ForkJoinPool 和 ThreadPoolExecutor 都是 ExecutorService(線程池)的實(shí)現(xiàn)盔夜,但ForkJoinPool 的獨(dú)特點(diǎn)在于:ThreadPoolExecutor 只能執(zhí)行 Runnable 和 Callable 任務(wù)负饲,而 ForkJoinPool 不僅可以執(zhí)行 Runnable 和 Callable 任務(wù)堤魁,還可以執(zhí)行 Fork/Join 型任務(wù)ForkJoinTask,從而滿足并行地實(shí)現(xiàn)分治算法的需要返十;
- ThreadPoolExecutor 中任務(wù)的執(zhí)行順序是按照其在共享隊(duì)列中的順序來(lái)執(zhí)行的妥泉,所以后面的任務(wù)需要等待前面任務(wù)執(zhí)行完畢后才能執(zhí)行,而 ForkJoinPool 每個(gè)線程有自己的任務(wù)隊(duì)列洞坑,并在此基礎(chǔ)上實(shí)現(xiàn)了 Work-Stealing 的功能盲链,使得在某些情況下 ForkJoinPool 能更大程度的提高并發(fā)效率。
除了JDK的文檔迟杂,本文還參考自:
《Java并發(fā)編程實(shí)戰(zhàn)》
并發(fā)編程網(wǎng) - Fork and Join: Java也可以輕松地編寫并發(fā)程序
并發(fā)編程網(wǎng) - 聊聊并發(fā)(八)——Fork/Join框架介紹
Java 多線程(5):Fork/Join 型線程池與 Work-Stealing 算法