java多線程之ForkJoinPool

來源:互聯網
上載者:User

標籤:https   poll   閱讀   cond   random   hold   線程建立   sub   compute   

轉https://www.cnblogs.com/lixuwu/p/7979480.html

 

閱讀目錄

  • 使用

背景:ForkJoinPool的優勢在於,可以充分利用多cpu,多核cpu的優勢,把一個任務拆分成多個“小任務”,把多個“小任務”放到多個處理器核心上並存執行;當多個“小任務”執行完成之後,再將這些執行結果合并起來即可。這種思想值得學習。

主要參考《瘋狂java講義》

回到頂部使用

Java7 提供了ForkJoinPool來支援將一個任務拆分成多個“小任務”並行計算,再把多個“小任務”的結果合并成總的計算結果。

ForkJoinPool是ExecutorService的實作類別,因此是一種特殊的線程池。

使用方法:建立了ForkJoinPool執行個體之後,就可以調用ForkJoinPool的submit(ForkJoinTask<T> task) 或invoke(ForkJoinTask<T> task)方法來執行指定任務了。

其中ForkJoinTask代表一個可以並行、合并的任務。ForkJoinTask是一個抽象類別,它還有兩個抽象子類:RecusiveAction和RecusiveTask。其中RecusiveTask代表有傳回值的任務,而RecusiveAction代表沒有傳回值的任務。

下面的UML類圖顯示了ForkJoinPool、ForkJoinTask之間的關係:

 

舉例

以還行沒有傳回值的“大任務”(簡單低列印1~300的數值)為例,程式將一個“大任務”拆分成多個“小任務”,並將任務交給ForkJoinPool來執行

/** * Project Name:Spring0725 * File Name:ForkJoinPoolAction.java * Package Name:work1201.basic * Date:2017年12月4日下午2:26:55 * Copyright (c) 2017, 深圳金融電子結算中心 All Rights Reserved. **/package work1201.basic;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.RecursiveAction;import java.util.concurrent.TimeUnit;/** * ClassName:ForkJoinPoolAction <br/> * Function: 使用ForkJoinPool完成一個任務的分段執行 * 簡單的列印0-300的數值。用多線程實現並存執行 * Date:     2017年12月4日 下午2:26:55 <br/> * @author   prd-lxw * @version   1.0 * @since    JDK 1.7 * @see       */public class ForkJoinPoolAction {        public static void main(String[] args) throws Exception{        PrintTask task = new PrintTask(0, 300);        //建立執行個體,並執行分割任務        ForkJoinPool pool = new ForkJoinPool();        pool.submit(task);         //線程阻塞,等待所有任務完成        pool.awaitTermination(2, TimeUnit.SECONDS);        pool.shutdown();    }}/** * ClassName: PrintTask <br/> * Function: 繼承RecursiveAction來實現“可分解”的任務。 * date: 2017年12月4日 下午5:17:41 <br/> * * @author prd-lxw * @version 1.0 * @since JDK 1.7 */class PrintTask extends RecursiveAction{    private static final int THRESHOLD = 50; //最多隻能列印50個數    private int start;    private int end;            public PrintTask(int start, int end) {        super();        this.start = start;        this.end = end;    }    @Override    protected void compute() {                if(end - start < THRESHOLD){            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();        }            }    }

 

執行結果:

ForkJoinPool-1-worker-1的i值:262ForkJoinPool-1-worker-7的i值:75ForkJoinPool-1-worker-7的i值:76ForkJoinPool-1-worker-5的i值:225ForkJoinPool-1-worker-3的i值:187ForkJoinPool-1-worker-6的i值:150ForkJoinPool-1-worker-6的i值:151ForkJoinPool-1-worker-6的i值:152ForkJoinPool-1-worker-6的i值:153ForkJoinPool-1-worker-6的i值:154......

 

因為我的電腦是i7處理器,一共8個cpu,觀察線程的名稱可以發現,8個cpu都在運行。

通過RecursiveTask的傳回值,來對一個長度為100的數組元素進行累加。

/** * Project Name:Spring0725 * File Name:ForJoinPollTask.java * Package Name:work1201.basic * Date:2017年12月4日下午5:41:46 * Copyright (c) 2017, 深圳金融電子結算中心 All Rights Reserved. **/package work1201.basic;import java.util.Random;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.Future;import java.util.concurrent.RecursiveTask;/** * ClassName:ForJoinPollTask <br/> * Function: 對一個長度為100的元素值進行累加 * Date:     2017年12月4日 下午5:41:46 <br/> * @author   prd-lxw * @version   1.0 * @since    JDK 1.7 * @see       */public class ForJoinPollTask {    public static void main(String[] args) throws Exception {        int[] arr = new int[100];        Random random = new Random();        int total =0;        //初始化100個數組元素        for(int i=0,len = arr.length;i<len;i++){            int temp = random.nextInt(20);            //對數組元素賦值,並將數組元素的值添加到sum總和中            total += (arr[i]=temp);        }        System.out.println("初始化數組總和:"+total);        SumTask task = new SumTask(arr, 0, arr.length);//        建立一個通用池,這個是jdk1.8提供的功能        ForkJoinPool pool = ForkJoinPool.commonPool();        Future<Integer> future = pool.submit(task); //提交分解的SumTask 任務        System.out.println("多線程執行結果:"+future.get());        pool.shutdown(); //關閉線程池                    }}/** * ClassName: SumTask <br/> * Function: 繼承抽象類別RecursiveTask,通過返回的結果,來實現數組的多線程分段累累加 *  RecursiveTask 具有傳回值 * date: 2017年12月4日 下午6:08:11 <br/> * * @author prd-lxw * @version 1.0 * @since JDK 1.7 */class SumTask extends RecursiveTask<Integer>{    private static final int THRESHOLD = 20; //每個小任務 最多隻累加20個數    private int arry[];    private int start;    private int end;            /**     * Creates a new instance of SumTask.     * 累加從start到end的arry數組     * @param arry     * @param start     * @param end     */    public SumTask(int[] arry, int start, int end) {        super();        this.arry = arry;        this.start = start;        this.end = end;    }    @Override    protected Integer compute() {        int sum =0;        //當end與start之間的差小於threshold時,開始進行實際的累加        if(end - start <THRESHOLD){            for(int i= start;i<end;i++){                sum += arry[i];            }            return sum;        }else {//當end與start之間的差大於threshold,即要累加的數超過20個時候,將大任務分解成小任務            int middle = (start+ end)/2;            SumTask left = new SumTask(arry, start, middle);            SumTask right = new SumTask(arry, middle, end);            //並存執行兩個 小任務            left.fork();            right.fork();            //把兩個小任務累加的結果合并起來            return left.join()+right.join();        }            }    }

 

執行結果:

初始化數組總和:1008多線程執行結果:1008

 

分析

在Java 7中引入了一種新的線程池:ForkJoinPool。

它同ThreadPoolExecutor一樣,也實現了Executor和ExecutorService介面。它使用了一個無限隊列來儲存需要執行的任務,而線程的數量則是通過建構函式傳入,如果沒有向建構函式中傳入希望的線程數量,那麼當前電腦可用的CPU數量會被設定為線程數量作為預設值。

ForkJoinPool主要用來使用分治法(Divide-and-Conquer Algorithm)來解決問題。典型的應用比如快速排序演算法。

這裡的要點在於,ForkJoinPool需要使用相對少的線程來處理大量的任務。

比如要對1000萬個資料進行排序,那麼會將這個任務分割成兩個500萬的排序任務和一個針對這兩組500萬資料的合并任務。以此類推,對於500萬的資料也會做出同樣的分割處理,到最後會設定一個閾值來規定當資料規模到多少時,停止這樣的分割處理。比如,當元素的數量小於10時,會停止分割,轉而使用插入排序對它們進行排序。

那麼到最後,所有的任務加起來會有大概2000000+個。問題的關鍵在於,對於一個任務而言,只有當它所有的子任務完成之後,它才能夠被執行。

所以當使用ThreadPoolExecutor時,使用分治法會存在問題,因為ThreadPoolExecutor中的線程無法像任務隊列中再添加一個任務並且在等待該任務完成之後再繼續執行。而使用ForkJoinPool時,就能夠讓其中的線程建立新的任務,並掛起當前的任務,此時線程就能夠從隊列中選擇子任務執行。

 

以上程式的關鍵是fork()和join()方法。在ForkJoinPool使用的線程中,會使用一個內部隊列來對需要執行的任務以及子任務進行操作來保證它們的執行順序。

那麼使用ThreadPoolExecutor或者ForkJoinPool,會有什麼效能的差異呢?

首先,使用ForkJoinPool能夠使用數量有限的線程來完成非常多的具有父子關係的任務,比如使用4個線程來完成超過200萬個任務。但是,使用ThreadPoolExecutor時,是不可能完成的,因為ThreadPoolExecutor中的Thread無法選擇優先執行子任務,需要完成200萬個具有父子關係的任務時,也需要200萬個線程,顯然這是不可行的。

ps:ForkJoinPool在執行過程中,會建立大量的子任務,導致GC進行記憶體回收,這些是需要注意的。

java多線程之ForkJoinPool

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.