【实战】ForkJoin处理大任务

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

ForkJoin用于并行执行任务的框架,把一个大任务拆分成多个小任务最后汇总每个小任务的执行结果得到大任务的执行结果

并行:当一个系统有多个CPU时,一个CPU执行一个进程时,另一个CPU执行另外一个进程,两个进程互相不抢占CPU,可以同时进行

举例:以1加到1亿为例,如果整个过程需要10分钟,将这个计算大任务拆成4个小任务一个小任务执行需要2分钟,几个任务同时执行整个过程执行完毕也就只需要两分钟,充分利用了CPU的计算资源

image.png

但是会出现一种现象任务分发,多个线程之间竞争的问题,所以就有一个 工作窃取算法

工作窃取算法

为了减少线程之间的竞争,把这些子任务分别放到不同的队列中,每个队列都有单独的线程来执行队列里面的任务

又有可能出现另外一种情况,A线程已经做完了他自己所在队列里面做的任务而另外还有线程没有完成

这时候有双端队列,为了不造成冲突,A线程从双端队列的尾部开始处理B线程未处理完的任务,而原本这个队列的B线程从双端队列的头部拿任务执行
image.png

demo

实例:求1+...+100的和

  1. 创建一个ForkJoin线程池
  2. 给出一个求和的任务
  3. 将任务交给线程池 (线程池会分解任务并合并结果)
  4. 获取结果并输出
  5. 关闭ForkJoin线程池

其中最重要的步骤就是第三步分解任务

完整代码如下:

public class TestTask extends RecursiveTask<Long> {

    int start; //从哪开始
    int end; //到哪结束
    int step = 10; //步长

    public TestTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        //1.定义结果变量
        Long sum = 0L;
        //2.计算结果
        if ((end - start) <= step) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            //分解任务
            int mid = (start + end) / 2;
            TestTask oneTask = new TestTask(start, mid);
            TestTask twoTask = new TestTask(mid + 1, end);

            //继续拆分
            oneTask.fork();
            twoTask.fork();

            //合并结果
            Long one = oneTask.join();
            Long two = twoTask.join();
            sum = one + two;

        }

        //3.返回结果
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1.创建一个ForkJoin线程池
        ForkJoinPool pool = new ForkJoinPool();

        //2.给出一个求和的任务
        RecursiveTask task = new TestTask(1, 100);

        //3.将任务交给线程池 (线程池会分解任务并合并结果)
        Future<Long> future = pool.submit(task);

        //4.获取结果并输出
        Long result = future.get();
        System.out.println(result);

        //5.关闭ForkJoin线程池
        pool.shutdown();
    }

}
复制代码

运行结果

image.png

和普通方式运行时间对比

以下是普通实现方式

image.png

运行时间对比

image.png

对比结果

image.png
从结果看出,使用ForkJoin方式时间反而用的更久,所以如果任务比较小就没必要去拆分了