最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java ForkJoin 框架全面解析:分而治之的并行編程藝術(shù)

 更新時(shí)間:2026年05月21日 09:38:11   作者:百錦再@新空間創(chuàng)想科技  
本文介紹了Java Fork/JoinJoin框架用于并行執(zhí)行任務(wù)的思想、核心組件和使用方法,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧

Java Fork/Join 框架是一種用于并行執(zhí)行任務(wù)的框架,它基于分而治之(divide-and-conquer)策略。這種策略將一個(gè)大任務(wù)分割成多個(gè)小任務(wù),然后將這些小任務(wù)并行執(zhí)行,最后合并這些小任務(wù)的執(zhí)行結(jié)果以得到最終結(jié)果。Fork/Join 框架在 Java 7 中被引入,并被設(shè)計(jì)為在 Java 8 中進(jìn)一步增強(qiáng)和優(yōu)化。

課程導(dǎo)言

適用對(duì)象

本課程適合已經(jīng)掌握J(rèn)ava多線程基礎(chǔ)(如Thread、Runnable、synchronized),并初步了解JUC并發(fā)工具(如線程池、ConcurrentHashMap)的開發(fā)者。ForkJoin框架是Java并發(fā)編程的高級(jí)主題,理解它將幫助你掌握"分而治之"的并行計(jì)算思想,為后續(xù)學(xué)習(xí)大數(shù)據(jù)處理框架(如MapReduce)打下堅(jiān)實(shí)基礎(chǔ)。

學(xué)習(xí)目標(biāo)

通過本文的系統(tǒng)學(xué)習(xí),你將能夠:

  • 理解 ForkJoin框架的核心思想:分治法與工作竊取算法
  • 掌握 ForkJoinPool、ForkJoinTask、RecursiveTask/RecursiveAction的核心API
  • 熟練使用 ForkJoin框架解決可分解的并行計(jì)算問題
  • 學(xué)會(huì) 任務(wù)粒度的選擇、性能調(diào)優(yōu)和常見陷阱規(guī)避
  • 了解 ForkJoin在現(xiàn)代Java并發(fā)生態(tài)中的應(yīng)用(如Parallel Stream)

為什么需要ForkJoin?

在并發(fā)編程中,我們經(jīng)常遇到一些可以"分而治之"的大任務(wù):比如遍歷超大數(shù)組求和、處理海量文件、計(jì)算復(fù)雜遞歸函數(shù)(如斐波那契數(shù)列)、并行排序等。將這些大任務(wù)拆分成小任務(wù)并行執(zhí)行,最后合并結(jié)果,往往能獲得巨大的性能提升。

傳統(tǒng)的ThreadPoolExecutor雖然也能處理多任務(wù),但它面臨一個(gè)核心挑戰(zhàn):當(dāng)任務(wù)之間存在父子依賴關(guān)系時(shí),如何高效調(diào)度? 例如,一個(gè)任務(wù)分解成兩個(gè)子任務(wù),這兩個(gè)子任務(wù)完成后才能繼續(xù)父任務(wù)。如果用傳統(tǒng)線程池,你需要手動(dòng)管理這些依賴,代碼復(fù)雜且容易出錯(cuò)。

ForkJoin框架正是JDK為這種場景提供的專門解決方案。它由并發(fā)大師Doug Lea設(shè)計(jì),自JDK 7引入,是java.util.concurrent包中最精巧、最高效的組件之一。

第一部分:核心思想——分治法 + 工作竊取

1.1 分治法:從大化小,逐個(gè)擊破

分治法(Divide-and-Conquer)是一種古老的算法思想,其核心可以用十二個(gè)字概括:分解、解決、合并。

  • 分解(Fork):將一個(gè)大的任務(wù)遞歸地拆分成若干個(gè)規(guī)模更小的子任務(wù),直到子任務(wù)簡單到可以直接計(jì)算(達(dá)到設(shè)定的閾值)。
  • 解決:并行執(zhí)行這些子任務(wù)。
  • 合并(Join):等待所有子任務(wù)完成,并將它們的結(jié)果按順序合并,得到最終結(jié)果。

這種思想天然適合并行處理。典型的應(yīng)用包括歸并排序、快速排序、大數(shù)求和、矩陣運(yùn)算等。

1.2 工作竊?。鹤詣?dòng)負(fù)載均衡的靈魂

工作竊?。╓ork-Stealing)算法是ForkJoin框架性能卓越的核心所在。它解決了傳統(tǒng)線程池中線程負(fù)載不均的問題。

為什么需要工作竊???

假設(shè)我們將一個(gè)大任務(wù)拆分成10個(gè)小任務(wù),并啟動(dòng)了5個(gè)線程。由于每個(gè)任務(wù)的執(zhí)行時(shí)間可能不同,有的線程很快完成了自己的任務(wù),有的線程還在忙。如果空閑線程只是等待,就浪費(fèi)了寶貴的CPU資源。工作竊取正是讓空閑線程主動(dòng)去幫助繁忙線程的一種機(jī)制。

工作竊取的實(shí)現(xiàn)原理

  1. 每個(gè)線程有自己的雙端隊(duì)列:在ForkJoinPool中,每個(gè)工作線程(ForkJoinWorkerThread)都維護(hù)著一個(gè)雙端隊(duì)列(Deque),用于存放分配給它的任務(wù)。
  2. 線程從隊(duì)列頭部獲取任務(wù):當(dāng)線程執(zhí)行自己的任務(wù)時(shí),采用**LIFO(后進(jìn)先出)**的順序從隊(duì)列頭部取出任務(wù)執(zhí)行。LIFO策略的優(yōu)勢(shì)在于:最近被推入的任務(wù)通常是最新拆分的子任務(wù),其相關(guān)數(shù)據(jù)很可能還在CPU緩存中,執(zhí)行效率更高。
  3. 竊取線程從隊(duì)列尾部偷任務(wù):當(dāng)一個(gè)線程的任務(wù)隊(duì)列為空時(shí),它不會(huì)閑著,而是隨機(jī)選擇一個(gè)其他線程的隊(duì)列,從該隊(duì)列的尾部竊取一個(gè)任務(wù)來執(zhí)行。竊取時(shí)采用**FIFO(先進(jìn)先出)**的順序。
  4. 雙端隊(duì)列減少競爭:這種設(shè)計(jì)巧妙地減少了競爭。因?yàn)楸桓`取線程(頭部操作)和竊取線程(尾部操作)通常操作隊(duì)列的不同端,只有在隊(duì)列中只剩一個(gè)任務(wù)時(shí)才會(huì)發(fā)生競爭,但這種情況的概率較低。

工作竊取的優(yōu)點(diǎn)

  • 自動(dòng)負(fù)載均衡:空閑線程自動(dòng)幫助繁忙線程,充分利用所有CPU核心。
  • 減少競爭:雙端隊(duì)列設(shè)計(jì)使大部分操作無鎖化。
  • 高效緩存利用:LIFO處理方式提高了緩存命中率。

缺點(diǎn):在某些極端情況下(如隊(duì)列只剩一個(gè)任務(wù)),仍存在競爭;同時(shí)維護(hù)多個(gè)雙端隊(duì)列也增加了系統(tǒng)開銷。

第二部分:ForkJoin框架核心組件

ForkJoin框架主要由三個(gè)核心組件構(gòu)成:

2.1 ForkJoinPool —— 任務(wù)調(diào)度器

ForkJoinPool是ForkJoin框架的線程池實(shí)現(xiàn),它繼承了AbstractExecutorService,因此也是一個(gè)特殊的ExecutorService。但與ThreadPoolExecutor不同,它的內(nèi)部不是用一個(gè)共享的任務(wù)隊(duì)列,而是維護(hù)了一個(gè)工作隊(duì)列數(shù)組WorkQueue[]),每個(gè)工作隊(duì)列對(duì)應(yīng)一個(gè)工作線程。

創(chuàng)建ForkJoinPool

// 方式一:使用默認(rèn)構(gòu)造器(并行度 = CPU核心數(shù))
ForkJoinPool pool1 = new ForkJoinPool();
// 方式二:指定并行度
ForkJoinPool pool2 = new ForkJoinPool(4); // 使用4個(gè)線程
// 方式三:使用公共池(推薦!)
ForkJoinPool commonPool = ForkJoinPool.commonPool();

關(guān)于公共池(commonPool):從JDK 8開始,ForkJoinPool提供了一個(gè)靜態(tài)的commonPool()方法,返回一個(gè)全局共享的線程池實(shí)例。官方強(qiáng)烈推薦大多數(shù)應(yīng)用程序使用這個(gè)公共池,因?yàn)樗梢怨?jié)省資源,讓多個(gè)ForkJoin任務(wù)共享同一個(gè)線程池,避免創(chuàng)建大量線程。公共池的線程在空閑時(shí)會(huì)慢慢回收,需要時(shí)再重新創(chuàng)建。Parallel Stream底層使用的正是這個(gè)公共池。

核心方法

方法描述
execute(ForkJoinTask)異步執(zhí)行任務(wù),無返回值
submit(ForkJoinTask)異步執(zhí)行任務(wù),返回Future對(duì)象
invoke(ForkJoinTask)同步執(zhí)行任務(wù),等待任務(wù)完成并返回結(jié)果
invokeAll(ForkJoinTask...)批量提交多個(gè)子任務(wù),等待所有完成

2.2 ForkJoinTask —— 任務(wù)的抽象

ForkJoinTask是提交給ForkJoinPool執(zhí)行的任務(wù)的基類。它提供了fork()join()等核心方法,并實(shí)現(xiàn)了Future接口。在實(shí)際開發(fā)中,我們幾乎從不直接繼承ForkJoinTask,而是繼承它的兩個(gè)抽象子類:

RecursiveTask —— 有返回值的任務(wù)

適用于需要返回計(jì)算結(jié)果的任務(wù),如數(shù)組求和、斐波那契數(shù)列計(jì)算。

核心方法protected abstract V compute(),你需要在這個(gè)方法中實(shí)現(xiàn)任務(wù)的分解和計(jì)算邏輯。

RecursiveAction —— 無返回值的任務(wù)

適用于只需要執(zhí)行動(dòng)作而不需要返回結(jié)果的任務(wù),如遍歷目錄、數(shù)組元素批量修改。

核心方法protected abstract void compute()。

fork() 與 join() 的奧秘

  • fork():異步執(zhí)行當(dāng)前任務(wù)。它并不是簡單地啟動(dòng)一個(gè)新線程,而是將當(dāng)前任務(wù)推入當(dāng)前工作線程的工作隊(duì)列(如果當(dāng)前線程是ForkJoinWorkerThread),或者推入ForkJoinPool的提交隊(duì)列。fork()方法會(huì)立即返回,不會(huì)阻塞。
  • join():等待任務(wù)執(zhí)行完成并獲取結(jié)果。如果任務(wù)尚未完成,join()會(huì)阻塞當(dāng)前線程,直到任務(wù)完成。在阻塞期間,如果當(dāng)前線程是工作線程,它不會(huì)閑著,而是會(huì)嘗試竊取并執(zhí)行其他任務(wù),以提高CPU利用率——這是ForkJoin框架設(shè)計(jì)的精妙之處。

關(guān)鍵理解fork()join()的配對(duì)使用,加上工作竊取機(jī)制,使得ForkJoin框架能夠用少量線程高效處理大量有依賴關(guān)系的任務(wù)。

2.3 ForkJoinWorkerThread —— 執(zhí)行任務(wù)的工作線程

這是執(zhí)行ForkJoinTask的線程。每個(gè)工作線程都關(guān)聯(lián)著一個(gè)自己的雙端隊(duì)列,用于存放它fork出來的子任務(wù)。工作線程的生命周期由ForkJoinPool統(tǒng)一管理。

第三部分:實(shí)戰(zhàn)案例——從入門到精通

理論知識(shí)講完了,現(xiàn)在通過三個(gè)由淺入深的實(shí)戰(zhàn)案例,帶你掌握ForkJoin的具體用法。

3.1 案例一:數(shù)組求和(RecursiveTask入門)

這是ForkJoin最經(jīng)典的入門案例。我們計(jì)算一個(gè)超大數(shù)組中所有元素的和。

代碼實(shí)現(xiàn)

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
 * 使用Fork/Join計(jì)算數(shù)組求和
 */
public class ArraySumCalculator extends RecursiveTask<Long> {
    private final int[] array;
    private final int start;
    private final int end;
    private static final int THRESHOLD = 10000; // 閾值:當(dāng)數(shù)組長度小于此值時(shí),不再拆分
    public ArraySumCalculator(int[] array) {
        this(array, 0, array.length);
    }
    private ArraySumCalculator(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        int length = end - start;
        // 1. 如果任務(wù)足夠小,直接計(jì)算(不再分解)
        if (length <= THRESHOLD) {
            return computeDirectly();
        }
        // 2. 任務(wù)拆分
        int mid = start + length / 2;
        ArraySumCalculator leftTask = new ArraySumCalculator(array, start, mid);
        ArraySumCalculator rightTask = new ArraySumCalculator(array, mid, end);
        // 3. 異步執(zhí)行左半部分任務(wù)(fork)
        leftTask.fork();
        // 4. 當(dāng)前線程繼續(xù)執(zhí)行右半部分(同步執(zhí)行)
        Long rightResult = rightTask.compute();
        // 5. 等待左半部分結(jié)果(join)
        Long leftResult = leftTask.join();
        // 6. 合并結(jié)果
        return leftResult + rightResult;
    }
    private long computeDirectly() {
        long sum = 0;
        for (int i = start; i < end; i++) {
            sum += array[i];
        }
        return sum;
    }
    public static void main(String[] args) {
        // 創(chuàng)建測(cè)試數(shù)組:1到10000000
        int[] array = new int[10_000_000];
        for (int i = 0; i < array.length; i++) {
            array[i] = i + 1;
        }
        // 使用ForkJoin計(jì)算
        ForkJoinPool pool = new ForkJoinPool();
        ArraySumCalculator task = new ArraySumCalculator(array);
        long startTime = System.currentTimeMillis();
        Long result = pool.invoke(task);
        long endTime = System.currentTimeMillis();
        System.out.println("計(jì)算結(jié)果: " + result);
        System.out.println("耗時(shí): " + (endTime - startTime) + "ms");
        // 驗(yàn)證結(jié)果(數(shù)學(xué)公式:n(n+1)/2)
        long expected = (long) array.length * (array.length + 1) / 2;
        System.out.println("結(jié)果正確: " + result.equals(expected));
        pool.shutdown();
    }
}

代碼詳解

  1. 閾值(THRESHOLD):決定何時(shí)停止拆分。過小會(huì)導(dǎo)致任務(wù)拆分過細(xì),增加調(diào)度開銷;過大會(huì)導(dǎo)致并行度不足。需要根據(jù)實(shí)際情況調(diào)整。
  2. compute()方法:核心邏輯。先判斷任務(wù)是否足夠小,是則直接計(jì)算;否則拆分成左右兩個(gè)子任務(wù)。
  3. fork()與compute()的配合:這里我們fork()了左任務(wù),而右任務(wù)由當(dāng)前線程同步執(zhí)行。這是一種常見的優(yōu)化寫法,比同時(shí)fork兩個(gè)任務(wù)再join更高效。
  4. join():等待左任務(wù)完成并獲取結(jié)果,然后合并。

為什么不是先fork兩個(gè)任務(wù)再join?

錯(cuò)誤的寫法:

leftTask.fork();
rightTask.fork();  // 這樣效率低下!
Long leftResult = leftTask.join();
Long rightResult = rightTask.join();

這種寫法會(huì)先fork兩個(gè)子任務(wù),然后join等待。問題是:fork之后,兩個(gè)子任務(wù)都進(jìn)入了工作隊(duì)列,等待被其他空閑線程竊取執(zhí)行。如果此時(shí)有空閑線程,沒問題;但如果沒有空閑線程,而當(dāng)前線程又在join等待,就會(huì)浪費(fèi)一個(gè)線程資源。正確的做法是:fork一個(gè)任務(wù),然后當(dāng)前線程同步執(zhí)行另一個(gè)任務(wù),這樣就能確保當(dāng)前線程在等待期間不會(huì)閑著。

3.2 案例二:斐波那契數(shù)列(遞歸任務(wù))

斐波那契數(shù)列是一個(gè)天然的遞歸問題,非常適合用ForkJoin來演示。

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class FibonacciTask extends RecursiveTask<Integer> {
    private final int n;
    public FibonacciTask(int n) {
        this.n = n;
    }
    @Override
    protected Integer compute() {
        if (n <= 1) {
            return n;
        }
        // 創(chuàng)建子任務(wù):f(n-1) 和 f(n-2)
        FibonacciTask f1 = new FibonacciTask(n - 1);
        FibonacciTask f2 = new FibonacciTask(n - 2);
        // 異步執(zhí)行f1
        f1.fork();
        // 同步執(zhí)行f2
        int result2 = f2.compute();
        // 獲取f1的結(jié)果
        int result1 = f1.join();
        return result1 + result2;
    }
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        int n = 10; // 計(jì)算第10個(gè)斐波那契數(shù)
        int result = pool.invoke(new FibonacciTask(n));
        System.out.println("Fibonacci(" + n + ") = " + result); // 輸出55
    }
}

注意:雖然這個(gè)例子展示了ForkJoin的使用,但斐波那契數(shù)列并不適合用ForkJoin,因?yàn)樗挠?jì)算量太小,而任務(wù)拆分開銷太大,實(shí)際性能比普通遞歸還差。這個(gè)例子僅用于理解API。

3.3 案例三:遍歷目錄統(tǒng)計(jì)文件(RecursiveAction實(shí)戰(zhàn))

這個(gè)案例更有實(shí)際意義:統(tǒng)計(jì)一個(gè)目錄及其子目錄下所有.java文件的數(shù)量。由于不需要返回值,我們使用RecursiveAction。

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
public class FileCounter extends RecursiveAction {
    private final File directory;
    private final String extension;
    private int count = 0; // 統(tǒng)計(jì)結(jié)果
    public FileCounter(File directory, String extension) {
        this.directory = directory;
        this.extension = extension;
    }
    public int getCount() {
        return count;
    }
    @Override
    protected void compute() {
        File[] files = directory.listFiles();
        if (files == null) return;
        List<FileCounter> subTasks = new ArrayList<>();
        for (File file : files) {
            if (file.isDirectory()) {
                // 創(chuàng)建子任務(wù)處理子目錄
                FileCounter subTask = new FileCounter(file, extension);
                subTask.fork(); // 異步執(zhí)行
                subTasks.add(subTask);
            } else if (file.getName().endsWith(extension)) {
                count++;
            }
        }
        // 等待所有子任務(wù)完成,并累加結(jié)果
        for (FileCounter subTask : subTasks) {
            subTask.join();
            count += subTask.getCount();
        }
    }
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        FileCounter task = new FileCounter(new File("/path/to/your/project"), ".java");
        pool.invoke(task); // 同步等待
        System.out.println("找到 " + task.getCount() + " 個(gè) .java 文件");
    }
}

第四部分:適用場景與注意事項(xiàng)

4.1 適用場景

ForkJoin框架最適合解決以下類型的問題:

場景類型示例說明
計(jì)算密集型任務(wù)大數(shù)組數(shù)學(xué)運(yùn)算、矩陣乘法任務(wù)需要大量CPU計(jì)算,分解后可以并行加速
可遞歸分解的任務(wù)歸并排序、快速排序、文件遍歷天然的分治結(jié)構(gòu)
任務(wù)之間相互獨(dú)立圖像處理(每個(gè)像素獨(dú)立)無需同步,沒有數(shù)據(jù)競爭
任務(wù)粒度適中每個(gè)子任務(wù)計(jì)算量在數(shù)萬到數(shù)百萬次操作太細(xì)則調(diào)度開銷大,太粗則并行度不足

4.2 不適用場景

場景類型原因
I/O密集型任務(wù)線程會(huì)在I/O操作時(shí)阻塞,浪費(fèi)CPU,且工作竊取無法發(fā)揮作用
需要頻繁同步的任務(wù)鎖競爭會(huì)抵消并行帶來的好處
任務(wù)粒度太細(xì)創(chuàng)建任務(wù)、調(diào)度、合并的開銷超過計(jì)算本身
無法分解的串行任務(wù)分治思想的前提就是可以分解

對(duì)于I/O密集型任務(wù),可以考慮配合ManagedBlocker使用,或者改用CompletableFuture

4.3 如何選擇合適的閾值?

閾值的選擇是ForkJoin調(diào)優(yōu)的關(guān)鍵。沒有固定的公式,一般遵循以下原則:

  1. 通過實(shí)驗(yàn)確定:編寫測(cè)試代碼,對(duì)不同閾值進(jìn)行壓測(cè),找出性能最優(yōu)值。
  2. 參考經(jīng)驗(yàn)值:對(duì)于簡單的數(shù)組遍歷,閾值在1000-10000之間比較常見。
  3. 動(dòng)態(tài)調(diào)整:高級(jí)用法中,可以通過getSurplusQueuedTaskCount()方法判斷當(dāng)前線程的負(fù)載,動(dòng)態(tài)決定是否繼續(xù)拆分。

4.4 常見陷阱與注意事項(xiàng)

陷阱1:在任務(wù)中執(zhí)行阻塞操作

如果在compute()方法中執(zhí)行了Thread.sleep()、等待I/O等阻塞操作,會(huì)導(dǎo)致工作線程被阻塞,無法執(zhí)行其他任務(wù),嚴(yán)重降低并行效率。解決方案是使用ForkJoinPool.ManagedBlocker接口,或者將阻塞部分放在CompletableFuture中處理。

陷阱2:忘記合并結(jié)果

// 錯(cuò)誤的做法:fork了子任務(wù)卻沒有join
leftTask.fork();
rightTask.fork();
// 這里應(yīng)該join,但沒有

陷阱3:任務(wù)拆分過深導(dǎo)致棧溢出

遞歸調(diào)用太深可能導(dǎo)致StackOverflowError??梢赃m當(dāng)增大閾值,或者采用非遞歸的實(shí)現(xiàn)方式。

陷阱4:錯(cuò)誤使用invokeAll

invokeAll()方法是批量提交任務(wù)的便捷方式,它會(huì)等待所有任務(wù)完成。但要注意,invokeAll()內(nèi)部已經(jīng)包含了fork操作,不需要再對(duì)子任務(wù)調(diào)用fork。

// 正確用法
invokeAll(leftTask, rightTask);
// 然后通過leftTask.join()獲取結(jié)果

陷阱5:忘記處理異常

ForkJoinTask在執(zhí)行過程中可能拋出異常,但異常不會(huì)直接傳播給調(diào)用者。需要通過isCompletedAbnormally()getException()方法檢查異常。

if (task.isCompletedAbnormally()) {
    Throwable ex = task.getException();
    ex.printStackTrace();
}

陷阱6:死鎖風(fēng)險(xiǎn)

雖然ForkJoin框架內(nèi)部不會(huì)死鎖,但如果你在任務(wù)中等待其他任務(wù)的結(jié)果時(shí)形成了循環(huán)依賴,仍然可能死鎖。確保任務(wù)依賴關(guān)系是樹形的,而不是環(huán)形的。

4.5 性能優(yōu)化策略

為了充分發(fā)揮ForkJoin的性能,可以采取以下優(yōu)化措施:

  1. 合理設(shè)置并行度:默認(rèn)等于CPU核心數(shù)。對(duì)于計(jì)算密集型任務(wù),這個(gè)值通常是合適的??梢酝ㄟ^-Djava.util.concurrent.ForkJoinPool.common.parallelism=N調(diào)整公共池的并行度。
  2. 使用compute()而不是fork().join():如前面案例所示,fork一個(gè)任務(wù),同步執(zhí)行另一個(gè),可以減少任務(wù)調(diào)度開銷。
  3. 避免任務(wù)粒度過細(xì):每個(gè)任務(wù)至少要有數(shù)千次操作,否則調(diào)度開銷會(huì)超過計(jì)算本身。
  4. 優(yōu)先使用公共池:除非有特殊需求,否則使用ForkJoinPool.commonPool()。
  5. 監(jiān)控和調(diào)優(yōu):通過getPoolSize()getActiveThreadCount()等方法監(jiān)控線程池狀態(tài),分析性能瓶頸。

第五部分:ForkJoin與現(xiàn)代Java并發(fā)生態(tài)

5.1 Parallel Stream(并行流)

從JDK 8開始,Stream API引入了并行流(.parallelStream()),其底層正是基于ForkJoin框架的公共池實(shí)現(xiàn)的。

// 使用并行流計(jì)算數(shù)組和
long sum = Arrays.stream(array).parallel().sum();

并行流封裝了ForkJoin的復(fù)雜性,讓開發(fā)者可以用聲明式的方式編寫并行代碼。但需要注意的是,并行流默認(rèn)使用公共池,對(duì)于某些I/O操作或阻塞操作,可能不是最佳選擇。

5.2 CompletableFuture

CompletableFuture是JDK 8引入的異步編程工具,它內(nèi)部使用了ForkJoinPool.commonPool()作為默認(rèn)的異步執(zhí)行器。你可以通過thenApplyAsync()、thenComposeAsync()等方法指定使用自定義的線程池,包括ForkJoinPool。

5.3 與其他并發(fā)框架的對(duì)比

框架適用場景優(yōu)點(diǎn)缺點(diǎn)
ForkJoin可分解的計(jì)算密集型任務(wù)高效利用CPU,自動(dòng)負(fù)載均衡不適合I/O任務(wù)
ThreadPoolExecutor通用任務(wù)處理靈活,可定制處理依賴任務(wù)復(fù)雜
CompletableFuture異步任務(wù)編排功能強(qiáng)大,支持鏈?zhǔn)秸{(diào)用學(xué)習(xí)曲線較陡
Parallel Stream集合數(shù)據(jù)處理聲明式,簡潔控制粒度較粗

第六部分:深入源碼(選讀)

6.1 ForkJoinPool的核心數(shù)據(jù)結(jié)構(gòu)

ForkJoinPool內(nèi)部維護(hù)了一個(gè)WorkQueue數(shù)組workQueues。每個(gè)WorkQueue是一個(gè)雙端隊(duì)列,存儲(chǔ)著ForkJoinTask。工作線程與隊(duì)列的對(duì)應(yīng)關(guān)系是:

  • 下標(biāo)為奇數(shù)的隊(duì)列:由工作線程獨(dú)占
  • 下標(biāo)為偶數(shù)的隊(duì)列:用于存放外部提交的任務(wù)(共享隊(duì)列)

ForkJoinPool還維護(hù)了一個(gè)復(fù)雜的控制信號(hào)量ctl,用于管理線程的狀態(tài)(活躍、等待、終止等)。

6.2 工作竊取的實(shí)現(xiàn)細(xì)節(jié)

當(dāng)工作線程自己的隊(duì)列為空時(shí),會(huì)調(diào)用scan()方法嘗試竊取。它會(huì)隨機(jī)選擇一個(gè)其他線程的隊(duì)列,從尾部獲取一個(gè)任務(wù)。為了防止競爭,這個(gè)操作使用了Unsafe的CAS方法。

如果竊取也失敗,線程會(huì)進(jìn)入等待狀態(tài),將自己掛起。當(dāng)有新任務(wù)提交時(shí),掛起的線程會(huì)被喚醒。

6.3 提交任務(wù)的流程

當(dāng)我們調(diào)用pool.invoke(task)時(shí),流程如下:

  1. 將任務(wù)放入ForkJoinPool的外部提交隊(duì)列(偶數(shù)下標(biāo))
  2. 如果當(dāng)前沒有活躍的工作線程,創(chuàng)建一個(gè)
  3. 工作線程從隊(duì)列中取出任務(wù)執(zhí)行
  4. 任務(wù)中的fork()會(huì)將子任務(wù)放入當(dāng)前工作線程自己的隊(duì)列(奇數(shù)下標(biāo))

到此這篇關(guān)于Java ForkJoin 框架全面解析:分而治之的并行編程藝術(shù)的文章就介紹到這了,更多相關(guān)Java ForkJoin 框架內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)samza轉(zhuǎn)換成flink

    Java實(shí)現(xiàn)samza轉(zhuǎn)換成flink

    將Apache Samza作業(yè)遷移到Apache Flink作業(yè)是一個(gè)復(fù)雜的任務(wù),因?yàn)檫@兩個(gè)流處理框架有不同的API和架構(gòu),本文我們就來看看如何使用Java實(shí)現(xiàn)samza轉(zhuǎn)換成flink吧
    2024-11-11
  • java組件fileupload文件上傳demo

    java組件fileupload文件上傳demo

    這篇文章主要為大家詳細(xì)介紹了java組件fileupload文件上傳demo ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Java中InputStream重復(fù)使用問題的幾種解決方案

    Java中InputStream重復(fù)使用問題的幾種解決方案

    在Java開發(fā)中,InputStream是用于讀取字節(jié)流的類,在許多場景下,我們可能需要重復(fù)讀取InputStream中的數(shù)據(jù),這篇文章主要介紹了Java中InputStream重復(fù)使用問題的幾種解決方案,需要的朋友可以參考下
    2025-08-08
  • Java運(yùn)算符與流程控制之全類型運(yùn)算符用法及分支?/?循環(huán)語句實(shí)戰(zhàn)指南

    Java運(yùn)算符與流程控制之全類型運(yùn)算符用法及分支?/?循環(huán)語句實(shí)戰(zhàn)指南

    這篇文章主要介紹了Java運(yùn)算符與流程控制之全類型運(yùn)算符用法及分支/循環(huán)語句實(shí)戰(zhàn)的相關(guān)資料,運(yùn)算符包括算術(shù)、關(guān)系、邏輯、賦值和三元運(yùn)算符,重點(diǎn)講解了它們的用法、優(yōu)先級(jí)和結(jié)合性,以及強(qiáng)制類型轉(zhuǎn)換的注意事項(xiàng),需要的朋友可以參考下
    2026-01-01
  • Spring Boot Mysql 數(shù)據(jù)庫操作示例

    Spring Boot Mysql 數(shù)據(jù)庫操作示例

    本篇文章主要介紹了Spring Boot Mysql 數(shù)據(jù)庫操作示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-02-02
  • Java基本數(shù)據(jù)類型之間的相互轉(zhuǎn)換詳解

    Java基本數(shù)據(jù)類型之間的相互轉(zhuǎn)換詳解

    這篇文章主要講解Java中基本數(shù)據(jù)類型的轉(zhuǎn)換,數(shù)據(jù)之間相互轉(zhuǎn)換是經(jīng)常會(huì)用到的基礎(chǔ)操作,文中講的很清晰,希望能給大家做一個(gè)參考。
    2022-05-05
  • Java基礎(chǔ)學(xué)習(xí)之字符串知識(shí)總結(jié)

    Java基礎(chǔ)學(xué)習(xí)之字符串知識(shí)總結(jié)

    今天帶著大家復(fù)習(xí)一下Java基礎(chǔ)知識(shí)-字符串,文中介紹的非常詳細(xì),對(duì)初步學(xué)習(xí)Java或者復(fù)習(xí)Java的小伙伴們都很有幫助喲,需要的朋友可以參考下
    2021-05-05
  • idea 隱藏target,iml等不需要展示的文件(推薦)

    idea 隱藏target,iml等不需要展示的文件(推薦)

    這篇文章主要介紹了idea 隱藏target,iml等不需要展示的文件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • SpringBoot對(duì)靜態(tài)資源的映射規(guī)則詳解解讀

    SpringBoot對(duì)靜態(tài)資源的映射規(guī)則詳解解讀

    這篇文章主要介紹了SpringBoot對(duì)靜態(tài)資源的映射規(guī)則詳解解讀,在Spring Boot中,映射規(guī)則是用來定義URL與控制器方法之間的映射關(guān)系的,通過映射規(guī)則,可以將特定的URL請(qǐng)求映射到相應(yīng)的控制器方法上,從而實(shí)現(xiàn)請(qǐng)求的處理和響應(yīng)的返回,需要的朋友可以參考下
    2023-10-10
  • SpringBoot日志框架之Log4j2快速入門與參數(shù)詳解

    SpringBoot日志框架之Log4j2快速入門與參數(shù)詳解

    本文介紹了SpringBoot日志框架log4j2的基本使用和配置方法,包括將日志輸出到控制臺(tái)、文件、Elasticsearch和Kafka,多個(gè)輸出目的地的配置,異步日志記錄器的使用以及l(fā)og4j2.xml配置文件的詳細(xì)語法和參數(shù)含義,需要的朋友可以參考下
    2023-05-05

最新評(píng)論

迭部县| 康马县| 和龙市| 平谷区| 黔西| 屯留县| 邢台市| 邢台市| 武功县| 罗甸县| 梁山县| 阳曲县| 海淀区| 尼木县| 漯河市| 家居| 华容县| 友谊县| 灵川县| 历史| 溆浦县| 南昌县| 思茅市| 札达县| 丰县| 怀化市| 比如县| 昌邑市| 湘阴县| 姜堰市| 海门市| 汉沽区| 沽源县| 江达县| 巫溪县| 辉县市| 霍邱县| 大庆市| 津市市| 丰顺县| 黄骅市|