聲明:本篇博客是在閱讀了引用博客的兩篇文章後做了簡短的概括與歸納,只作爲自己筆記
文章目錄
一、思想
Fork/Join是Java7提供的並行執行任務的框架,是一個把大人物分割成若干小任務,最終彙總小任務的結果得到大任務結果的框架
小任務可以繼續拆分爲更小的任務
二、工作竊取算法
1、工作竊取會選擇雙端隊列作爲存儲任務的數據結構,默認正常線程會選擇LIFO(棧獲取)的方式,從當前雙端隊列的尾部獲取任務;竊取線程會選擇FIFO(隊列獲取)方式,從當前雙端隊列的頭部獲取任務
默認添加元素是從雙端隊列的尾部添加元素
三、demo用例
通過Fork/Join並行計算1+2+3+4
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
public class ForkjoinDemo extends RecursiveTask<Long> {
private long start;
private long end;
public static final int THRESHOLD = 2;
public ForkjoinDemo(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum = 0;
boolean flag = (end - start) <= THRESHOLD;
if (flag) {
for (long i = start; i <= end; i++) {
sum += i;
}
} else {
long middle = (end + start) / 2;
// divide task
ForkjoinDemo leftTask = new ForkjoinDemo(start, middle);
ForkjoinDemo rightTask = new ForkjoinDemo(middle+1 , end);
// execute sub task
leftTask.fork();
rightTask.fork();
// get result sub task
long left = leftTask.join();
long right = rightTask.join();
sum = left + right;
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
ForkjoinDemo task = new ForkjoinDemo(1, 4);
Future<Long> result = pool.submit(task);
try {
System.out.println(result.get());
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
四、關鍵組件
ForkJoinPool
(一)線程池的作用可能是用來合理分配線程資源:
1、接受外部任務的提交(外部調用ForkJoinPool的invoke/execute/submit方法提交任務);
2、接受ForkJoinTask自身fork出的子任務的提交;
3、任務隊列數組(WorkQueue[])的初始化和管理;
4、工作線程(Worker)的創建/管理
(二)提交方式的差異:
invoke:同步提交,有返回值,任務執行完成後返回
submit:異步提交,有返回值,調用線程立即返回
execute:異步提交,無返回值,調用線程立即返回
(三)初始化線程池方式
1、FIFO_QUEUE:先進先出,異步模式
2、LIFO_QUEUE:(默認)先進後出,同步模式
ForkJoinTask
(一)子類
1、RecursiveAction:用於沒有返回結果的任務
2、RecursiveTask:用於有返回結果的任務
(二)函數
1、fork()拆分爲子任務
2、join()合併子任務
ForkJoinWorkerThread
任務處理原則:首先根據同步/異步模式從任務隊列選擇任務,如果完成自身任務,通過竊取算法獲取其他線程的任務
WorkQueue
底層是通過數組實現的雙端隊列,容量爲2的冪次,任務隊列在首次調用線程池外部方法提交任務之後初始化任務隊列,通過ThreadLocalRandom.probe來計算出任務隊列在數組中的索引位置(外部方法調用產生的索引一定是偶數),沒有綁定工作線程
1、有工作線程(Worker)綁定的任務隊列:數組下標始終是奇數,稱爲task queue,該隊列中的任務均由工作線程調用產生(工作線程調用FutureTask.fork方法);
2、沒有工作線程(Worker)綁定的任務隊列:數組下標始終是偶數,稱爲submissions queue,該隊列中的任務全部由其它線程提交(也就是非工作線程調用execute/submit/invoke或者FutureTask.fork方法)。
五、Fork/Join運行流程圖
任務提交
(一)外部提交任務
1、提交任務命中,externalPush
2、提交任務未命中,externalSubmit
CASE1:線程池已經關閉,則執行終止操作,並拒絕該任務的提交;
CASE2:線程池未初始化,則進行初始化,主要就是初始化任務隊列數組;
CASE3:命中了任務隊列,則將任務入隊,並嘗試創建/喚醒一個工作線程(Worker);
CASE4:未命中任務隊列,則在偶數索引處創建一個任務隊列
(二)工作線程fork任務:直接push進當前任務隊列
創建線程signalWork方法
1、工作線程數不足:
創建一個工作線程:
(1)通過createWorker方法通過線程池工廠創建線程,在線程創建的過程中,registerWorker會創建一個工作隊列與工作線程綁定,創建成功,則start線程;
(2)創建失敗:則deregisterWorker註銷該工作線程,清除已關閉的工作線程或回滾創建線程之前的操作,並把傳入的異常拋給 ForkJoinTask 來處理。
2、工作線程數足夠:喚醒一個空閒(阻塞)的工作線程。
任務執行
1、runWorker執行任務
(1)scan()竊取任務:隨機選擇一個任務隊列,獲取底部任務,任務數大於1,則signalWork創建或喚醒其他工作線程。
(2)成功竊取runTask:
a.執行竊取任務:調用FutureTask.deExec()執行任務,其內部會調用FutureTask.exec()方法,該方法爲抽象方法,由子類實現。
b.工作線程還會執行自己隊列中的任務,即WorkQueue.execLocalTasks
(3)未成功竊取awaitWork:沒有任務則阻塞
六、引用博客
極力推薦併發系列博客:https://segmentfault.com/a/1190000016781127