本文原始碼:GitHub·點這裡 || GitEE·點這裡
一、Fork/Join框架
Java提供Fork/Join框架用於並行執行任務,核心的思想就是將一個大任務切分成多個小任務,然後彙總每個小任務的執行結果得到這個大任務的最終結果。
這種機制策略在分散式資料庫中非常常見,資料分佈在不同的資料庫的副本中,在執行查詢時,每個服務都要跑查詢任務,最後在一個服務上做資料合併,或者提供一箇中間引擎層,用來彙總資料:
核心流程:切分任務,模組任務非同步執行,單任務結果合併;在程式設計裡面,通用的程式碼不多,但是通用的思想卻隨處可見。
二、核心API和方法
1、編碼案例
基於1+2..+100的計算案例演示Fork/Join框架基礎用法。
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
public class ForkJoin01 {
public static void main (String[] args) {
int[] numArr = new int[100];
for (int i = 0; i < 100; i++) {
numArr[i] = i + 1;
}
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Integer> forkJoinTask =
pool.submit(new SumTask(numArr, 0, numArr.length));
System.out.println("合併計算結果: " + forkJoinTask.invoke());
pool.shutdown();
}
}
/**
* 執行緒任務
*/
class SumTask extends RecursiveTask<Integer> {
/*
* 切分任務塊的閾值
* 如果THRESHOLD=100
* 輸出:main【求和:(0...100)=5050】 合併計算結果: 5050
*/
private static final int THRESHOLD = 100;
private int arr[];
private int start;
private int over;
public SumTask(int[] arr, int start, int over) {
this.arr = arr;
this.start = start;
this.over = over;
}
// 求和計算
private Integer sumCalculate () {
Integer sum = 0;
for (int i = start; i < over; i++) {
sum += arr[i];
}
String task = "【求和:(" + start + "..." + over + ")=" + sum +"】";
System.out.println(Thread.currentThread().getName() + task);
return sum ;
}
@Override
protected Integer compute() {
if ((over - start) <= THRESHOLD) {
return sumCalculate();
}else {
int middle = (start + over) / 2;
SumTask left = new SumTask(arr, start, middle);
SumTask right = new SumTask(arr, middle, over);
left.fork();
right.fork();
return left.join() + right.join();
}
}
}
2、核心API說明
ForkJoinPool:執行緒池最大的特點就是分叉(fork)合併(join)模式,將一個大任務拆分成多個小任務,並行執行,再結合工作竊取演算法提高整體的執行效率,充分利用CPU資源。
ForkJoinTask:執行在ForkJoinPool的一個任務抽象,可以理解為類執行緒但是比執行緒輕量的實體,在ForkJoinPool中執行的少量ForkJoinWorkerThread可以持有大量的ForkJoinTask和它的子任務,同時也是一個輕量的Future,使用時應避免較長阻塞或IO。
繼承子類:
- RecursiveAction:遞迴無返回值的ForkJoinTask子類;
- RecursiveTask:遞迴有返回值的ForkJoinTask子類;
核心方法:
- fork():在當前執行緒執行的執行緒池中建立一個子任務;
- join():模組子任務完成的時候返回任務結果;
- invoke():執行任務,也可以實時等待最終執行結果;
3、核心策略說明
任務拆分
ForkJoinPool基於分治演算法,將大任務不斷拆分下去,每個子任務再拆分一半,直到達到最閾值設定的任務粒度為止,並且把任務放到不同的佇列裡面,然後從最底層的任務開始執行計算,並且往上一層合併結果,這樣用相對少的執行緒處理大量的任務。
工作竊取演算法
大任務被分割為獨立的子任務,並且子任務分別放到不同的佇列裡,併為每個佇列建立一個執行緒來執行佇列裡的任務,假設執行緒A優先把分配到自己佇列裡的任務執行完畢,此時如果執行緒E對應的佇列裡還有任務等待執行,空閒的執行緒A會竊取執行緒E佇列裡任務執行,並且為了減少竊取任務時執行緒A和被竊取任務執行緒E之間的發生競爭,竊取任務的執行緒A會從佇列的尾部獲取任務執行,被竊取任務執行緒E會從佇列的頭部獲取任務執行。
工作竊取演算法的優點:執行緒間的競爭很少,充分利用執行緒進行平行計算,但是在任務佇列裡只有一個任務時,也可能會存在競爭情況。
三、應用案例分析
在後端系統的業務開發中,可用做許可權校驗,批量定時任務狀態重新整理等各種功能場景:
如上圖,假設資料的主鍵id分段如下,資料場景可能是資料來源的連線資訊,或者產品有效期類似業務,都可以基於執行緒池任務處理:
許可權校驗
基於資料來源的連線資訊,判斷資料來源是否可用,例如:判斷連線是否可用,使用者是否有庫表的讀寫許可權,在資料來源多的情況下,基於執行緒池快速校驗。
狀態重新整理
在定時任務中,經常見到狀態類的重新整理操作,例如判斷產品是否在有效期範圍內,在有效期範圍之外,把資料置為失效狀態,都可以利用執行緒池快速處理。
四、原始碼地址
GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent
推薦閱讀:Java併發系列
序號 | 文章標題 |
---|---|
01 | Java併發:執行緒的建立方式,狀態週期管理 |
02 | Java併發:執行緒核心機制,基礎概念擴充套件 |
03 | Java併發:多執行緒併發訪問,同步控制 |
04 | Java併發:執行緒間通訊,等待/通知機制 |
05 | Java併發:悲觀鎖和樂觀鎖機制 |
06 | Java併發:Lock機制下API用法詳解 |