Java併發程式設計(07):Fork/Join框架機制詳解

知了一笑發表於2020-08-16

本文原始碼: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併發程式設計(07):Fork/Join框架機制詳解

推薦閱讀:Java併發系列

序號 文章標題
01 Java併發:執行緒的建立方式,狀態週期管理
02 Java併發:執行緒核心機制,基礎概念擴充套件
03 Java併發:多執行緒併發訪問,同步控制
04 Java併發:執行緒間通訊,等待/通知機制
05 Java併發:悲觀鎖和樂觀鎖機制
06 Java併發:Lock機制下API用法詳解

相關文章