Forkjoin框架原理解析

聲明:本篇博客是在閱讀了引用博客的兩篇文章後做了簡短的概括與歸納,只作爲自己筆記

一、思想

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章