并发编程-个人对ForkJion的一些浅显理解

ForkJoin

采用分治的思想,将一个大的任务拆分成小任务,在把每个小任务都交给一个线程去处理,如果有线程提前处理完任务,可以工作窃取也就是从其他线程的任务队列尾部里偷取任务,帮他处理。

之前简单学过ForkJion,后来有个问题一直困扰我,并发只是线程间的来回切换谁分配到时间片谁执行,在某一时候是只有一个线程在执行的,并不会节约时间,会提高CPU的使用率,但是为什么用ForkJion却比单线程执行的时间短。今天正好看到了ForkJion,又重新看了下之前的代码,终于发现了问题所在。

之前就是做的从1加到1亿,分别用单线程和ForkJion做了一下时间的对比,发现ForkJion会快一些,其实快就快在,单线程情况下数是一直累加了数越大累加运算耗时就越高,而ForkJion对任务进行了拆分所以用时短一些。

总结:

正确使用ForkJion才会提高效率,错用效率可能比单线程还低。

①如果任务不能拆分ForkJion是不能使用的

②任务量小,线程间切换会涉及上下文的切换会有额外的时间消耗,可能效率比单线程还低。

③需求不同,效率不同。比如累加这种需求就挺适合用ForkJion的,任务拆分后子任务会轻松一些,而有的需求就不合适了。

代码:

package day02.forkjoin;

/**
 * @Author 小浩
 * @Date 2019/11/9 22:27
 * @Version 1.0
 **/
public class Array {
    public  static int SIZE = 100000000;
    public  static int[] getArray(){
        int arr[] = new int[SIZE];
        for (int i=0;i<SIZE;i++){
            arr[i]=i;
        }
        return  arr;
    }
}
package day02.forkjoin;


import java.util.concurrent.RecursiveTask;

/**
 * @Author 小浩
 * @Date 2019/11/9 22:34
 * @Version 1.0
 **/

/**
 * RecursiveAction 不带返回值    RecursiveTask 带返回值
 */
public class Task extends RecursiveTask<Integer> {
    private static int TASK_SIZE = Array.SIZE / 10;
    private int[] arr;
    private int left;
    private int right;

    public Task(int[] arr, int left, int right) {
        this.arr = arr;
        this.left = left;
        this.right = right;
    }

    @Override
    protected Integer compute() {
        if (right - left <= TASK_SIZE) {
            System.out.println("left:" + left + "right:" + right);
            System.out.println(Thread.currentThread().getName());
            int count = 0;
            for (int i = left; i <= right; i++) {

                count += arr[i];
            }
            return count;

        } else {
            int mid = (left + right) / 2;
            Task leftTask = new Task(arr, left, mid);
            Task rightTask = new Task(arr, mid + 1, right);
            invokeAll(leftTask, rightTask);
            return leftTask.join() + rightTask.join();
        }
    }
}
package day02.forkjoin;


import java.util.concurrent.*;

/**
 * @Author 小浩
 * @Date 2019/11/9 22:01
 * @Version 1.0
 **/

/**
 * 采用forkjoin框架多线程处理任务
 * 调用execute和submit时 异步执行可能任务处理不完线程就关闭了 此时可以用join方法 或者让主线程睡眠等待
 */
public class ForkJoinTest {
    public static void main(String[] args) {
      int arr[] = Array.getArray();

      long begin = System.currentTimeMillis();
        ForkJoinPool pool = new ForkJoinPool();
        Task task = new Task(arr,0,Array.SIZE-1);
//          调用execute方法异步执行 没有返回值
//          pool.execute(task);
//          调用submit方法异步执行 有返回值  调用get方法时同样会使main线程进入阻塞状态执行完才会执行下面代码 相当于同步执行
//          pool.submit(task);

//      调用invoke方法 同步执行 会使main线程进入阻塞状态执行完才会执行下面代码
        System.out.println( pool.invoke(task));

        long end = System.currentTimeMillis();

        System.out.println(end-begin);


    }
}
package day02.forkjoin;


/**
 * @Author 小浩
 * @Date 2019/11/9 22:50
 * @Version 1.0
 **/

/**
 * 单线程处理任务 与forkjoin框架多线程 进行比较
 */
public class Test {
    public static void main(String[] args) {
        int arr[] = Array.getArray();
      int count = 0;

      long begin = System.currentTimeMillis();
        System.out.println(begin);
      for (int i = 0 ;i <Array.SIZE ;i++){

          count = count + arr[i];
      }

        long end = System.currentTimeMillis();
        System.out.println(count);
        System.out.println(end);
        System.out.println(end-begin);

    }
}

 

从1累加到1000万结果对比:

使用ForkJion:

单线程情况下:

从1累加到10亿结果对比:

使用ForkJion:

单线程情况下:

© 版权声明
THE END
喜欢就支持以下吧
点赞0
分享
评论 共3条
    • 4213187
    • 4213187作者0

      很显然,值越界了但是不影响我们要测试的结果!

      1月前回复
    • 4213187
    • 4213187作者0

      补充一下,当异步使用ForkJionPool时,主线程关闭后ForkJionPool也会被关闭。理论上线程只要启动了,只有等所有线程执行完,进程才会关闭,后来查看ForkJionPool相关源码后发现里边的线程是守护线程。。。。。。。。。 线程池无论同步调用还是异步调用都不会出现这种问题,当然线程池不关闭是因为调用阻塞队列的take的方法,后续博客可能会发一个线程池的简单源码剖析。

      1月前回复
    • 4213187
    • 4213187作者0

      补充一点,ForkJionPoo默认启动的线程数是逻辑内核数,比如我的i5 6代 就是4 ,这时候基本涉及不到线程之间的切换,也就涉及不到上下文切换。

      16天前回复