Fork/Join以及FutureTask的原理分析(含RecursiveAction、RecursiveTask )

FutureTask

首先講一下FutureTask,它表示的是一種,異步操作的典範。我提交了任務,在未來我要拿到結果。

  考慮一種簡單的場景,甲問乙一個問題,乙一時回答不了,乙要去考慮一段時間(查一下資料),等到有結果了,再告訴甲。

  這時,我們需要類甲,類乙。

它的使用原理可以博主之前的篇文章:https://blog.csdn.net/qq_41864967/article/details/100737036(需要實現Callable接口或者Runable)

Future類就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。

必要時,通過get方法獲取執行結果,該方法會阻塞直到任務返回結果。

FutureTask實現了RunnableFuture,所以它既可以作爲Runnable被線程執行,又可以作爲Future得到Callback的返回值。

 public interface RunnableFuture<V> extends Runnable, Future<V> {  
        void run();  
    } 

分析:FutureTask除了實現了Future接口外還實現了Runnable接口(即可以通過Runnable接口實現線程,也可以通過Future取得線程執行完後的結果),因此FutureTask也可以直接提交給Executor執行。

 

Fork/Join

Fork/Join 框架是Java7提供的一個用於並行執行任務的框架。

是一個把大任務分割成若干個小任務,最終彙總每個小任務結果後得到大任務結果的框架。

 

工作竊取算法
         工作竊取(work-stealing)算法是指某個線程從其他隊列裏竊取任務來執行。
        
        假如需要做一個比較大的任務,我們可以把這個任務分割爲若干互不依賴的子任務,爲了減少線程間的競爭,於是把這些子任務分別放到不同的隊列裏,併爲每個隊列創建一個單獨的線程來執行隊列裏的任務,線程和隊列一一對應,比如A線程負責處理A隊列裏的任務。但是有的線程會先把自己隊列裏的任務幹完,而其他線程對應的隊列裏還有任務等待處理。幹完活的線程與其等着,不如去幫其他線程幹活,於是它就去其他線程的隊列裏竊取一個任務來執行。而在這時它們會訪問同一個隊列,所以爲了減少竊取任務線程和被竊取任務線程之間的競爭,通常會使用雙端隊列,被竊取任務線程永遠從雙端隊列的頭部拿任務執行,而竊取任務的線程永遠從雙端隊列的尾部拿任務執行。

fork/join框架的核心是ForkJoinPool類,該類繼承了AbstractExecutorService類。ForkJoinPool實現了工作竊取算法並且能夠執行 ForkJoinTask任務。

公共抽象類ForkJoinTask


這個類的就是fork/join的基石類了。實現了Future接口。可以看成是一個輕量級的Future。

兩個重要方法:

//安排在當前任務運行的池中異步執行此任務,有自定義線程池的話,就在自定義線程池裏面,沒有,就在公共線程池裏面。

public final ForkJoinTask<V> fork() {
        Thread t;
        if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
            ((ForkJoinWorkerThread)t).workQueue.push(this);
        else
            ForkJoinPool.common.externalPush(this);
        return this;
    }

 

當isDone()返回true時候,返回結果。
/**
     * Returns the result of the computation when it {@link #isDone is
     * done}.  This method differs from {@link #get()} in that
     * abnormal completion results in {@code RuntimeException} or
     * {@code Error}, not {@code ExecutionException}, and that
     * interrupts of the calling thread do <em>not</em> cause the
     * method to abruptly return by throwing {@code
     * InterruptedException}.
     *
     * @return the computed result
     */
    public final V join() {
        int s;
        if ((s = doJoin() & DONE_MASK) != NORMAL)
            reportException(s);
        return getRawResult();
    }

  join()和future接口的get()方法作用很相似區別是 join()方法不會拋出異常,get()會拋出異常。這兩個都會阻塞等待結果。

 

公共類ForkJoinPool

fork/join專用線程池,和其他線程池最大的區別是,這個線程池裏面的線程,會嘗試查找 任務執行。其他線程池則需要自己提交。實現了work-stealing(工作竊取)算法。

構造方法:

可以由 boolean asyncMode 決定是否異步。

一般的,我們調用ForkJoinPool .commonPool()返回一個實例。這是系統幫我們定義好的,可以使用公共的線程池。

提交任務的幾種方法:

void execute(ForkJoinTask<?> task) 異步提交執行,調用其fork方法在多個線程之間拆分工作,然後通過task的get(),或者join()得到結果。
<T> ForkJoinTask<T> submit​(ForkJoinTask<T> task) 提交ForkJoinTask以供執行,完成時返回一個future對象用於檢查狀態以及運行結果。然後通過返回的ForkJoinTask的get(),或者join()得到結果。
<T> T invoke(ForkJoinTask<T> task) 執行給定任務,完成後返回結果,會等待獲得結果。
List < Future > invokeAll(Collection <?extends Callable > tasks)執行給定的任務,返回完成所有狀態和結果的Futures列表。

 fork/join 就是特殊的線程池(ForkJoinPool)和特殊的future(ForkJoinTask)的配合。

官方寫了兩個具體的ForkJoinTask子類,我們平時使用時候,只需要extend 子類就行了。

 

ForkJoinTask子

  1. RecursiveAction 用於大多數不返回結果的計算
  2. RecursiveTask 會返回最終結果

我們繼承上面的類的時候,重寫compute()方法就行。 

使用例子如下:

RecursiveTask 例子:

public class Fibonacci extends RecursiveTask<Integer> {

    final int[] n;
    final int start;
    final int end;

    /**
     * 任務分段 判斷值
     */
    final int part = 1000_0;

    /**
     * @param n     數組
     * @param start 計算區間 開始
     * @param end   計算區間 結束
     */
    public Fibonacci(int[] n, int start, int end) {
        this.n = n;
        this.start = start;
        this.end = end;
        if (start > end || start < 0 || end > n.length) {
            throw new IllegalArgumentException();
        }
    }

    @Override
    protected Integer compute() {
        //小於計算空間 直接求和
        if ((end - start) <= part) {
            //可變參數本質就是先創建了一個數組,該數組的大小就是可變參數的個數
            return sum(start, end, n);
        }
        //進行任務切割 劃分
        Fibonacci f1 = new Fibonacci(n, start, start + part);
        //安排在當前任務運行的池中異步執行此任務(如果適用)
        f1.fork();//異步執行 也就是用另一個線程進行運算
        Fibonacci f2 = new Fibonacci(n, start + part, end);
        //join 返回計算結果,這個類似於get, 這個不會拋出異常
        return f2.compute() + f1.join();
    }


    private int sum(int start, int end, int... ints) {
        int temp = 0;
        for (int i = start; i < end; i++) {
            temp += ints[i];
        }
        return temp;
    }

}

test類:

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        int[] longs = new int[1000_000_0];
        for (int i = 0; i < 1000_000_0; i++) {
            longs[i] = i;
        }
        Fibonacci fibonacci = new Fibonacci(longs, 0, longs.length);
        long before = System.currentTimeMillis();

        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println(">>>>>>>>>>>>>>>>>>>>>>>" + fibonacci.isDone());
            }
        };
        Timer timer = new Timer();
        timer.schedule(timerTask, 0, 1000);
        //可以自定義 forkjoin線程池
        ForkJoinPool forkJoinPool = new ForkJoinPool(100);
        //使用公共線程池提交任務
        int result = ForkJoinPool.commonPool().invoke(fibonacci);
        long time = System.currentTimeMillis() - before;
        long before1 = System.currentTimeMillis();
        int result1 = sum(longs);
        long time1 = System.currentTimeMillis() - before1;
        System.out.println(result + " " + time + "   " + result1 + "  " + time1);

    }

    public static int sum(int[] ints) {
        int temp = 0;
        for (int i = 0; i < ints.length; i++) {
            temp += ints[i];
        }
        return temp;
    }
}

RecursiveAction 例子:

public class PrintTask extends RecursiveAction {
    private final int Max = 50;
    private int start;
    private int end;
    public PrintTask(int start,int end){
        this.start = start;
        this.end = end;
    }
    @Override
    protected void compute() {
        if((end - start)<Max){
            for(int i=start;i<end;i++){
                System.out.println("當前線程:"+Thread.currentThread().getName()+" i :"+i);
            }
        }else{
            int middle = (start+end)/2;
            PrintTask left = new PrintTask(start, middle);
            PrintTask right = new PrintTask(middle, end);
            left.fork();
            right.fork();
        }
    }

}

test類:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        ForkJoinPool forkJoin = new ForkJoinPool();
        forkJoin.submit(new PrintTask(0, 200));
        forkJoin.awaitTermination(2, TimeUnit.SECONDS);
        forkJoin.shutdown();
    }
}

結果:

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