ForkJoin 併發

爲什麼用 Fork/Join ?

對於簡單的並行任務,你可以通過“線程池+Future”的方案來解決;如果任務之間有聚合關係,無論是AND聚合還是OR聚合,都可以通過CompletableFuture來解決;而批量的並行任務,則可以通過CompletionService來解決。這幾種方案基本上能夠覆蓋日常工作中的併發場景了,但還是不夠全面,因爲還有一種“分治”的任務模型沒有覆蓋到

分治,顧名思義,即分而治之,是一種解決複雜問題的思維方法和模式;具體來講,指的是把一個複雜的問題分解成多個相似的子問題,然後再把子問題分 解成更小的子問題,直到子問題簡單到可以直接求解

Java併發包裏提供了一種叫做Fork/Join的並行計算框架,就是用來支持分治這種任務模型的。
 

 

 

如何用Fork/Join 並行計算框架計算斐波那契數列

   f(n)=f(n-1)+f(n-2)
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class forkjoin {
    public static void main(String[] args) {
        //創建分治任務線程池
        ForkJoinPool fjp = new	ForkJoinPool(4);
        //創建分治任務
        Fibonacci fib = new	Fibonacci(30);
        //啓動分治任務
        Long start_time = System.currentTimeMillis();
        Integer	result = fjp.invoke(fib);
        //輸出結果
        Long end_time = System.currentTimeMillis();
        Long compute_time = end_time - start_time;
        System.out.println("result: "+result);
        System.out.println("forkjoin compute_time: "+ compute_time);
    }
    //遞歸任務
    static class Fibonacci extends RecursiveTask<Integer>{
        final	int	n;
        Fibonacci(int	n){this.n	=	n;}
        protected	Integer	compute(){
            if(n<=1) 
                return n;
            Fibonacci f1 = new Fibonacci(n-1);
            //創建⼦任務
            f1.fork();
            Fibonacci f2 = new Fibonacci(n-2);
            //等待⼦任務結果,併合並結果
            return f2.compute()+f1.join();
        }
    }
}

普通單線程計算斐波那契數列

public class fibonacci {
    public static void main(String[] args) {
        Fibonacci fib = new	Fibonacci(30);
        Long start_time = System.currentTimeMillis();
        Integer	result = fib.compute();
        Long end_time = System.currentTimeMillis();
        Long compute_time = end_time - start_time;
        System.out.println("result: "+result);
        System.out.println("compute_time: "+ compute_time);
    }
    //遞歸任務
    static class Fibonacci {
        final int n;
        Fibonacci(int n){
            this.n = n;
        }
        protected	Integer	compute(){
            //終止條件
            if(n<=1)
                return	n;
            //創建⼦任務
            Fibonacci f1 = new	Fibonacci(n-1);
            Fibonacci f2 = new	Fibonacci(n-2);
            //等待⼦任務結果,併合並結果
            return f2.compute()+f1.compute();
        }
    }
}

分別執行,會發現普通計算的方式會更快,說明forkjoin在調度方面會有很大的性能消耗。

在終止條件下加上一條休眠語句,使每次計算都要持續設定時間以上,再對比兩種方式的速度。

 Fibonacci fib = new	Fibonacci(5);

 //終止條件
if(n<=1) {
    if(n<=1) {
         try {
              Thread.sleep(1000);
         }catch (Exception e){
              e.printStackTrace();
         }
         return n;
     }
}

可以發現並行的計算方式的執行速度,與設定的的線程數量有關,比如第五個斐波那契數列的值,需要分解爲8次計算任務,每次至少需要8秒。普通計算方式需要8秒,並行計算方式在線程數爲4的情況下,執行時間爲2秒;線程數爲8的情況下,執行時間爲1秒。實際執行速度應與CPU的核數有關,即如果CPU只有4核,就算設置爲8線程,最快速度也只有2秒(示例中執行時間爲1秒是因爲採用線程休眠來模擬該線程的總處理時間,實際該線程在休眠期間並不消耗計算資源)

 

使用Fork/Join需要注意的地方

在使用Fork/Join時,需要注意出現工作線程不工作的事情。下面從不同的使用方式來分析: fork() 和 compute()

  在示例中,下面這句語句是分解任務以及合併任務的關鍵

 f1.fork();
//等待⼦任務結果,併合並結果
return f2.compute()+f1.join();

 主線程分配了一個任務給子線程,同時自己也執行一次計算任務。

方式1: a.fork(); b.fork(); b.join(); a.join();

f1.fork();
f2.fork();
return f2.join()+f1.join();

  這種使用方式內部做了很多優化,目的都是爲了避免出現工作線程只分配任務而不執行任務的情況。

方式2:invokeAll(a,b)

invokeAll(f1, f2);
return f2.join()+f1.join();

    invokeAll的N個任務中,其中N-1個任務會使用fork()交給其它線程執行,但是,它還會留一個任務自己執行,這樣,就充分利用了線程池,保證沒有空閒的不幹活的線程。 

方式3:a.fork(); b.fork(); a.join(); b.join();    (錯誤的使用方式,不建議使用

f1.fork();
f2.fork();
return f1.join()+f2.join();

       這種使用方式會出現工作線程只分配任務給子線程,自己卻不執行任務的情況。比如某個任務可以分爲四個子任務,線程a把任務分給線程b線程c,而線程b線程c又繼續分給自己的子線程(b分解爲b1和b2 / c分解爲c1和c2),總共需要7個線程,但是abc卻不參與計算。而第一種使用方法,Java內部做了優化。

總結

用兩次fork()在join的時候,需要用這樣的順序:a.fork(); b.fork(); b.join(); a.join();這個要求在JDK官方文檔裏有說明。

建議使用fork() 和 compute() ,這樣可以避免避免性能問題,且更容易理解。該用法可理解爲在主線程中使用fork分解一次任務,同時該線程執行一次compute 計算,如果該compute計算還需要分解任務,則繼續fork() 和 compute(),直到滿足終止條件直接返回。因此主線程會不斷分解任務,直到任務無法分解並計算該任務返回計算結果。即主線程的處理任務是分解n/2 次任務以及執行一次無法分解的任務。

 

參考 :

https://www.liaoxuefeng.com/article/001493522711597674607c7f4f346628a76145477e2ff82000 

https://time.geekbang.org/discuss/detail/90130/

 

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