forkjoin線程框架

    fork英文含義爲叉子也可以理解爲拆分,join英文含義爲加入也可以理解爲彙集,所以forkjoin可以理解爲拆分任務然後將結果匯聚在一起,這種思想和大數據中的MapReduce很像(input --> split --> map --> reduce --> output),所以其可以大致分爲兩步:任務拆分和結果合併。

   下面我將以一個demo來演示下forkjoin的基本特質。

package com.dongnaoedu.network.humm.多線程.ForkJoin;

import java.sql.Time;
import java.util.ArrayList;
import java.util.concurrent.*;

/**
 * @author Heian
 * @time 19/07/28 22:24
 * @description: 理解forkjoin(線程池 +任務拆分 )的原理
 */
public class ForkJoinDemo {

    //自定義的任務
    static ArrayList<String> urls = new ArrayList<String>(){
        {
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
        }
    };

    //模擬網絡請求,假設這裏請求耗時爲100毫秒
    public static String doRequest(String url) throws InterruptedException{
        TimeUnit.MILLISECONDS .sleep (100);
        return "訪問的網址:"+url + "\n";
    }

    //設置我們需要的任務,從多少到多少位一組算一個任務
    static  class TaskGroup implements Callable<String>{
        private int startIndex;
        private int endIndex;

        public TaskGroup(int startIndex,int endIndex) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        }

        @Override
        public String call() throws Exception {
            String sb = "";
            for (int i = startIndex-1; i <=endIndex-1 ; i++) {
                String s = doRequest (urls.get (i));
                sb+=s;
            }
            return sb;
        }
    }


    /**
     *  拆分任務
     * @param pageSize 按照多大爲一組,拆分任務
     */
    public static void splitTask(int pageSize) throws ExecutionException, InterruptedException {
        int size = urls.size ();
        int groupCount = size/pageSize + 1; //   9/10 = 0
        System.out.println ("任務大小爲:" + size + ",分成" + groupCount + "組來處理");
        ExecutorService executorService = Executors.newFixedThreadPool (4);
        ArrayList<Future<String>> list = new ArrayList<> ();
        long startTime = System.currentTimeMillis ();
        /*------------------任務分組邏輯-----------------------*/
        //因爲可能最後一組會存在零星的,所以要單獨拿出來
        for (int i = 1; i <= groupCount-1; i++) {
            int startPageNum = (i-1)*pageSize + 1;//起始頁碼 = (第幾組-1)*pageSize +1 從1開始
            int endPageNum = pageSize*i;  //截止頁碼 = 第幾組*pageSize
            System.out.println (startPageNum + ":" + endPageNum);
            Future<String> future = executorService.submit (new TaskGroup (startPageNum, endPageNum));
            list.add (future);
        }
        //零星  最後一組
        int startPageNum = (groupCount-1)*pageSize + 1;//起始頁碼 = (第幾組-1)*pageSize +1 從1開始
        int endPageNum = size;  //截止頁碼 = 第幾組*pageSize
        System.out.println (startPageNum + ":" + endPageNum);
        Future<String> future = executorService.submit (new TaskGroup (startPageNum, endPageNum));
        list.add (future);
        for (Future<String> item : list){
            //拿到每組任務的返回值(很長的網址拼接) 
            System.out.println(item.get());//阻塞 每個組的任務會在換行
        }
        System.out.println ("耗時爲" + (System.currentTimeMillis ()-startTime) + "毫秒");


    }


    /**
     * 我們要做的就是模擬,將我們要訪問的網址,拆分成多組任務,後交給線程池處理
     */
    public static void main(String[] args) throws Exception{
        splitTask(10);
    }


}

        首先我這裏定義了一個String類型的字符串(有65個),來模擬網絡請求(每個請求耗時100ms),然後定義了一個線程池(4個線程)去請求這網址,所以這裏可以拆分非10個一組,然後就是7組,然後將每組返回的結果合併返回給結果集。控制檯打印結果如下:

任務大小爲:65,分成7組來處理
1:10
11:20
21:30
31:40
41:50
51:60
61:65
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com

訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com

訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com

訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com

訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com

訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com

訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com
訪問的網址:http://www.sina.com
訪問的網址:http://www.baidu.com

耗時爲2024毫秒

而如果要用forkjoin線程框架去實現它又該怎麼做呢?首先ForkJoinTask有兩個子類

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

你定義的任務類必須去實現ForkJoinTask的子類,繼而實現它的compute()方法,此方法就是去實現具體的拆分任務的邏輯,可類比我上面的splitTask(int pageSize)方法,這裏我將任務拆分爲2等分,然後兩等分在繼續拆分,知道拆分的邊界符合我的預期<=10,即可,圖解和實示例代碼如下。

  

package com.dongnaoedu.network.humm.多線程.ForkJoin;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author Heian
 * @time 19/07/28 22:24
 * @description: 理解forkjoin(線程池 +任務拆分 )的原理
 */
public class ForkJoinTest {

    //自定義的任務
    static ArrayList<String> urls = new ArrayList<String>(){
        {
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
            add("http://www.sina.com");
            add("http://www.baidu.com");
        }
    };

    //模擬網絡請求,假設這裏請求耗時爲100毫秒
    public static String doRequest(String url,int index) throws InterruptedException{
        TimeUnit.MILLISECONDS .sleep (100);
        return index + "-訪問的網址:"+url + "\n";
    }

    //本質是一個線程池,默認的線程數量:CPU的核數
    static ForkJoinPool forkJoinPool = new ForkJoinPool (Runtime.getRuntime().availableProcessors(),//我是4核
            ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, false);

    /**
     * 現在將一個大任務分成多組任務,而分組的邊界有自己設定,直到邊界不能再細分
     */
    static class Job extends RecursiveTask<String>{
        private List<String> list;//需要拆分的任務
        private int start;
        private int end;

        public Job(List<String> list, int start, int end) {
            this.list = list;
            this.start = start;
            this.end = end;
        }

        //不斷的拆分任務,直到任務數小於10纔不拆分,
        @Override
            protected String compute() {
            int taskSize = end - start;//得到任務的大小
            if (taskSize<=10){
                //先把任務拆分好了,在將各組任務執行
                System.out.println ("小於10" + Thread.currentThread ().getName ());
                String result = "";
                for (int i = start; i <end ; i++) {
                    try {
                        result += doRequest (urls.get (i),i);
                    } catch (InterruptedException e) {
                        e.printStackTrace ();
                    }
                }
                return result;
            }else {
                //拆分任務
                int x = (start + end)/2;//將任務分成兩份  奇數:3--> 1,2
                System.out.println (x+ Thread.currentThread ().getName ());
                //起三個線程去分解這些任務
                Job job1 = new Job (urls,start,x);
                ForkJoinTask<String> fork = job1.fork ();
                Job job2 = new Job (urls,x,end);
                ForkJoinTask<String> fork1 = job2.fork ();
                //固定寫法  類似於語法
                String result = "";
                result += job1.join ();
                result += job2.join ();
                return result;
            }

        }
    }


    /**
     * 我們要做的就是模擬,將我們要訪問的網址,拆分成多組任務,後交給線程池處理
     */
    public static void main(String[] args) throws Exception{
        long statrTime = System.currentTimeMillis ();
        Job job = new Job (urls,0,urls.size ());
        ForkJoinTask<String> result = forkJoinPool.submit (job);
        System.out.println (result.get ());
        System.out.println ("耗時:" + (System.currentTimeMillis ()-statrTime));

    }


}

控制檯信息如下:

32ForkJoinPool-1-worker-1
16ForkJoinPool-1-worker-2
8ForkJoinPool-1-worker-2
小於10ForkJoinPool-1-worker-2
48ForkJoinPool-1-worker-3
40ForkJoinPool-1-worker-3
24ForkJoinPool-1-worker-1
小於10ForkJoinPool-1-worker-1
小於10ForkJoinPool-1-worker-3
小於10ForkJoinPool-1-worker-0
小於10ForkJoinPool-1-worker-3
小於10ForkJoinPool-1-worker-1
56ForkJoinPool-1-worker-0
小於10ForkJoinPool-1-worker-0
小於10ForkJoinPool-1-worker-0
0-訪問的網址:http://www.baidu.com
1-訪問的網址:http://www.sina.com
2-訪問的網址:http://www.baidu.com
3-訪問的網址:http://www.sina.com
4-訪問的網址:http://www.baidu.com
5-訪問的網址:http://www.sina.com
6-訪問的網址:http://www.baidu.com
7-訪問的網址:http://www.sina.com
8-訪問的網址:http://www.baidu.com
9-訪問的網址:http://www.sina.com
10-訪問的網址:http://www.baidu.com
11-訪問的網址:http://www.sina.com
12-訪問的網址:http://www.baidu.com
13-訪問的網址:http://www.sina.com
14-訪問的網址:http://www.baidu.com
15-訪問的網址:http://www.sina.com
16-訪問的網址:http://www.baidu.com
17-訪問的網址:http://www.sina.com
18-訪問的網址:http://www.baidu.com
19-訪問的網址:http://www.sina.com
20-訪問的網址:http://www.baidu.com
21-訪問的網址:http://www.sina.com
22-訪問的網址:http://www.baidu.com
23-訪問的網址:http://www.sina.com
24-訪問的網址:http://www.baidu.com
25-訪問的網址:http://www.sina.com
26-訪問的網址:http://www.baidu.com
27-訪問的網址:http://www.sina.com
28-訪問的網址:http://www.baidu.com
29-訪問的網址:http://www.sina.com
30-訪問的網址:http://www.baidu.com
31-訪問的網址:http://www.sina.com
32-訪問的網址:http://www.baidu.com
33-訪問的網址:http://www.sina.com
34-訪問的網址:http://www.baidu.com
35-訪問的網址:http://www.sina.com
36-訪問的網址:http://www.baidu.com
37-訪問的網址:http://www.sina.com
38-訪問的網址:http://www.baidu.com
39-訪問的網址:http://www.sina.com
40-訪問的網址:http://www.baidu.com
41-訪問的網址:http://www.sina.com
42-訪問的網址:http://www.baidu.com
43-訪問的網址:http://www.sina.com
44-訪問的網址:http://www.baidu.com
45-訪問的網址:http://www.sina.com
46-訪問的網址:http://www.baidu.com
47-訪問的網址:http://www.sina.com
48-訪問的網址:http://www.baidu.com
49-訪問的網址:http://www.sina.com
50-訪問的網址:http://www.baidu.com
51-訪問的網址:http://www.sina.com
52-訪問的網址:http://www.baidu.com
53-訪問的網址:http://www.sina.com
54-訪問的網址:http://www.baidu.com
55-訪問的網址:http://www.sina.com
56-訪問的網址:http://www.baidu.com
57-訪問的網址:http://www.sina.com
58-訪問的網址:http://www.baidu.com
59-訪問的網址:http://www.sina.com
60-訪問的網址:http://www.baidu.com
61-訪問的網址:http://www.sina.com
62-訪問的網址:http://www.baidu.com
63-訪問的網址:http://www.sina.com
64-訪問的網址:http://www.baidu.com

耗時:2559

可以發現按道理線程池處理6500毫秒的任務,理想情況是6500/4=1625,但方法一返回的結果爲2024,方法2返回的結果爲2559,看出對於小任務,ForkJoin並不佔優,畢竟要處理不斷去拆分的邏輯,而且我這裏是定時操作,也就很難看出任務竊取了,這裏只做演示而已,只是要明白每個線程內部都有自己的隊列,並且是雙向隊列FIFO( First in, First out)先進先出,LIFO(Last in, First out)後進先出。

備註:還需研究源碼,這裏暫時不做深入考究,他日有時間在深究。

  1. Worker線程用LIFO的方法取出任務,後進隊列的任務先取出來(子任務總是後加入隊列,但是需要先執行)
  2. 當任務隊列爲空,會隨機從其他的worker的隊列中以FIFO拿走一個任務執行(工作竊取:steal work
  3. 如果一個Worker線程遇到了join操作,有子任務沒有,會等到這個任務結束。否則直接返回
  4. 如果一個Worker線程遇到了join操作,有子任務沒有,會等到這個任務結束。否則直接返回
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章