一、ForkJoinPool 分支合併框架
ForkJoinPool類實現了ExecutorService接口,因此也屬於線程池,是一種特殊的線程池。
ForkJoinPool這個工具類從Java7 纔開始提供的,優勢在於,可以充分利用多cpu,多核cpu的優勢,把一個大任務(fork)成若干小任務,把若干小任務放到多個處理器核心上並行執行;當這些小任務執行完成之後,最終彙總(Join)每個小任務結果後得到大任務結果的框架。從而實現用少量的線程,完成大數量的任務。
1、ThreadPoolExecutor 與 ForkJoinPool區別
ForkJoinPool與ThreadPoolExecutor兩個都實現了Executor和ExecutorService接口。
ForkJoinPool它使用了一個無限隊列來保存需要執行的任務,而線程的數量則是通過構造函數傳入,如果沒有向構造函數中傳入希望的線程數量,那麼當前計算機可用的CPU數量會被設置爲線程數量作爲默認值。使用ForkJoinPool能夠使用數量有限的線程來完成非常多的具有父子關係的任務,
ThreadPoolExecutor能夠提高線程的可管理性,通過重用已存在的線程,降低線程創建和銷燬造成的消耗;提高系統響應速度。但是,由於ThreadPoolExecutor中的Thread無法選擇優先執行子任務,所以不能完成父子關係的任務。
2、使用方法
創建 ForkJoinPool 實例,然後調用ForkJoinPool的 submit(ForkJoinTask<T> task) 或invoke(ForkJoinTask<T> task)方法來執行指定任務了。其中 ForkJoinTask代表一個可以並行、合併的任務。
ForkJoinTask是一個抽象類,它還有兩個抽象子類:RecusiveAction和RecusiveTask。其中RecusiveTask代表有返回值的任務,而RecusiveAction代表沒有返回值的任務。
-
-
<T> ForkJoinTask<T>
submit(ForkJoinTask<T> task)
提交forkjointask執行。
ForkJoinTask<?>
submit(Runnable task)
提交執行一個Runnable任務並返回一個表示該任務的未來。
<T> ForkJoinTask<T>
submit(Runnable task, T result)
提交執行一個Runnable任務並返回一個表示該任務的未來。
<T> T
invoke(ForkJoinTask<T> task)
完成給定的任務,完成後返回其結果。
<T> List<Future<T>>
invokeAll(Collection<? extends Callable<T>> tasks)
執行給定的任務,返回一個未來持有他們的狀態和結果的列表時,所有的完整。
boolean
awaitTermination(long timeout, TimeUnit unit)
直到所有的任務都完成後,關閉請求,或超時發生,或當前線程被中斷,以先發生的情況。
static ForkJoinPool
commonPool()
返回公用池實例。
-
實例一:沒有返回值的“大任務”(打印1-3000之間的整數)- RecusiveAction
public class ForkJoinPoolDemo1 {
public static void main(String[] args) throws Exception {
PrintTask task = new PrintTask(1, 300);
// 創建ForkJoinPool實例,並執行分割任務,
// 若參數爲空,則當前計算機可用的CPU數量會被設置爲線程數量作爲默認值
ForkJoinPool pool = new ForkJoinPool(4);
pool.submit(task);
// 線程阻塞,等待所有任務完成
pool.awaitTermination(2, TimeUnit.SECONDS);
// 關閉線程池
pool.shutdown();
}
}
class PrintTask extends RecursiveAction {
private static final int THRESHOLD = 50; //一次最多隻能打印50個數
private int start;
private int end;
public PrintTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end - start < THRESHOLD) {
for (int i = start; i < end; i++) {
System.out.println(Thread.currentThread().getName() + "的i值:" + i);
}
} else {
// 把大任務對半拆分成小任務,使用遞歸
int middle = (start + end) / 2;
PrintTask left = new PrintTask(start, middle);
PrintTask right = new PrintTask(middle, end);
//並行執行兩個小任務
left.fork();
right.fork();
}
}
}
實例二:有返回值的“大任務”(對一個長度爲300的數組元素進行累加求和)- RecusiveTask
public class ForkJoinPoolDemo2 {
public static void main(String[] args) throws Exception {
int[] arr = new int[300];
Random random = new Random();
int total = 0;
// 初始化100個數組元素
for (int i = 0, len = arr.length; i < len; i++) {
int temp = random.nextInt(20);
// 對數組元素賦值,並將數組元素的值添加到sum總和中
total += (arr[i] = temp);
}
System.out.println("初始化數組總和:" + total);
SumTask task = new SumTask(arr, 0, arr.length);
// 創建ForkJoinPool,這個是jdk1.8提供的功能
ForkJoinPool pool = ForkJoinPool.commonPool();
Future<Integer> future = pool.submit(task); //提交分解的SumTask 任務
System.out.println("多線程執行結果:" + future.get());
//關閉線程池
pool.shutdown();
}
}
class SumTask extends RecursiveTask<Integer> {
private static final int THRESHOLD = 20; //每個小任務 最多隻累加20個數
private int arry[];
private int start;
private int end;
public SumTask(int[] arry, int start, int end) {
super();
this.arry = arry;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
//當end與start之間的差小於threshold時,開始進行實際的累加
if (end - start < THRESHOLD) {
for (int i = start; i < end; i++) {
sum += arry[i];
}
return sum;
} else {
//當end與start之間的差大於threshold,即要累加的數超過20個時候,將大任務分解成小任務
int middle = (start + end) / 2;
SumTask left = new SumTask(arry, start, middle);
SumTask right = new SumTask(arry, middle, end);
//並行執行兩個小任務
left.fork();
right.fork();
//把兩個小任務累加的結果合併起來
return left.join() + right.join();
}
}
}
參考文章:
—— Stay Hungry. Stay Foolish. 求知若飢,虛心若愚。