【詳解】JUC之ForkJoin框架

簡介

從JDK1.7開始,Java提供ForkJoin框架用於並行執行任務,它的思想就是將一個大任務分割成若干小任務,最終彙總每個小任務的結果得到這個大任務的結果。

整個流程需要三個類完成

1、ForkJoinPool

既然任務是被逐漸的細化的,那就需要把這些任務存在一個池子裏面,這個池子就是ForkJoinPool。

它與其它的ExecutorService區別主要在於它使用“工作竊取“,那什麼是工作竊取呢?

一個大任務會被劃分成無數個小任務,這些任務被分配到不同的隊列,這些隊列有些幹活乾的塊,有些幹得慢。於是幹得快的,一看自己沒任務需要執行了,就去隔壁的隊列裏面拿去任務執行。

2、ForkJoinTask

ForkJoinTask就是ForkJoinPool裏面的每一個任務。他主要有兩個子類:RecursiveActionRecursiveTask。然後通過fork()方法去分配任務執行任務,通過join()方法彙總任務結果,

在這裏插入圖片描述

  • RecursiveAction 一個遞歸無結果的ForkJoinTask(沒有返回值)

  • RecursiveTask 一個遞歸有結果的ForkJoinTask(有返回值)

需要注意的是這兩個子類都是抽象類,需要繼承實現。

舉例說明

我們舉個例子:如果要計算一個超大數組的和,最簡單的做法是用一個循環在一個線程內完成:

┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

還有一種方法,可以把數組拆成兩部分,分別計算,最後加起來就是最終結果,這樣可以用兩個線程並行執行:

┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

如果拆成兩部分還是很大,我們還可以繼續拆,用4個線程並行執行:

┌─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┐
└─┴─┴─┴─┴─┴─┘

這就是Fork/Join任務的原理:判斷一個任務是否足夠小,如果是,直接計算,否則,就分拆成幾個小任務分別計算。這個過程可以反覆“裂變”成一系列小任務。

編碼實現

整個任務流程如下所示

  • 首先繼承任務,覆寫任務的執行方法
  • 通過判斷閾值,判斷該線程是否可以執行
  • 如果不能執行,則將任務繼續遞歸分配,利用fork方法,並行執行
  • 如果是有返回值的,才需要調用join方法,彙集數據。

RecursiveTask

這是一個有返回值的返回值的子類

public class RecursiveTaskTest {

    private final static int MAX_THRESHOLD = 3;//設置一個任務處理最大的閾值


    public static void main(String[] args) {
        final ForkJoinPool joinPool = new ForkJoinPool();
        ForkJoinTask<Integer> future = joinPool.submit(new CalculatedRecursiveTask(0, 1000));
        try {
            Integer integer = future.get();
            System.out.println("執行結果:" + integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }


    private static class CalculatedRecursiveTask extends RecursiveTask<Integer> {


        private final int start;//任務開始的上標
        private final int end;//任務開始的下標

        private CalculatedRecursiveTask(int start, int end) {
            this.start = start;
            this.end = end;
        }

        @Override
        protected Integer compute() {
            if (end - start <= MAX_THRESHOLD) {//如果自己能處理,就自己計算
                return IntStream.rangeClosed(start, end).sum();
            } else {//自己處理不了,拆分任務
                int middle = (end + start) / 2;
                CalculatedRecursiveTask leftTask = new CalculatedRecursiveTask(start, middle);
                CalculatedRecursiveTask rightTask = new CalculatedRecursiveTask(middle + 1, end);

                leftTask.fork();
                rightTask.fork();

                return leftTask.join() + rightTask.join();
            }
        }
    }
}

結果:

執行結果:500500

RecursiveAction

這是一個沒有返回值的返回值的子類

public class ForkJoinRecursiveAction {

    private final static int MAX_THRESHOLD = 3;//設置一個任務處理最大的閾值

    private final static AtomicInteger SUM = new AtomicInteger();


    public static void main(String[] args) throws InterruptedException {
        ForkJoinPool forkJoinPool = new ForkJoinPool();

        forkJoinPool.submit(new CalculateRecursiveAction(0,1000));

        forkJoinPool.awaitTermination(10, TimeUnit.SECONDS);

        System.out.println("執行結果爲:" + SUM);
    }

    private static class CalculateRecursiveAction extends RecursiveAction{

        private final int start;
        private final int end;

        private CalculateRecursiveAction(int start, int end) {
            this.start = start;
            this.end = end;
        }

        @Override
        protected void compute() {
            if ((end-start)<=MAX_THRESHOLD){
                SUM.addAndGet(IntStream.rangeClosed(start,end).sum());
            }else {
                int middle = (start+end)/2;
                CalculateRecursiveAction leftAction = new CalculateRecursiveAction(start,middle);
                CalculateRecursiveAction rightAction = new CalculateRecursiveAction(middle+1,end);
                leftAction.fork();
                rightAction.fork();
            }
        }
    }
}

結果:

執行結果:500500

支持Runnable和Callable

public void execute(Runnable task) {
    if (task == null)
        throw new NullPointerException();
    ForkJoinTask<?> job;
    if (task instanceof ForkJoinTask<?>) // avoid re-wrap
        job = (ForkJoinTask<?>) task;
    else
        job = new ForkJoinTask.RunnableExecuteAction(task);
    externalPush(job);
}



public <T> ForkJoinTask<T> submit(Callable<T> task) {
    ForkJoinTask<T> job = new ForkJoinTask.AdaptedCallable<T>(task);
    externalPush(job);
    return job;
}

從源碼上可以看到,即使不繼承兩個子類,也可以提交任務

總結

  • Fork/Join是一種基於“分治”的算法:通過分解任務,並行執行,最後合併結果得到最終結果。

  • ForkJoinPool線程池可以把一個大任務分拆成小任務並行執行,任務類必須繼承自RecursiveTask或RecursiveAction。

  • 使用Fork/Join模式可以進行並行計算以提高效率。

  • Java標準庫提供的java.util.Arrays.parallelSort(array)可以進行並行排序,它的原理就是內部通過Fork/Join對大數組分拆進行並行排序,在多核CPU上就可以大大提高排序的速度。

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