并发编程-分而治之框架ForkJoin

什么是Forkjoin

ForkJoin框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

我们再通过Fork和Join这两个单词来理解下Fork/Join框架,Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结果。比如计算1+2+。。+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和,最终汇总这10个子任务的结果。Fork/Join的运行流程图如下:

并发编程-分而治之框架ForkJoin_第1张图片

使用步骤

第一步分割任务。首先我们需要有一个fork类来把大任务分割成子任务,有可能子任务还是很大,所以还需要不停的分割,直到分割出的子任务足够小。

第二步执行任务并合并结果。分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据。

Fork/Join使用两个类来完成以上两件事情:

ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join()操作的机制,通常情况下我们不需要直接继承ForkJoinTask类,而只需要继承它的子类,Fork/Join框架提供了以下两个子类:
RecursiveAction:用于没有返回结果的任务。
RecursiveTask :用于有返回结果的任务。
ForkJoinPool :ForkJoinTask需要通过ForkJoinPool来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。

ForkJoinPool池
ForkJoinPool 顾名思义就是一个存放任务的池子,通过把若干的分解后的小任务放置在池子里面,它与其它的ExecutorService区别主要在于它使用“工作窃取“,那工作窃取又是什么呢?

一个大任务会被划分成无数个小任务,这些任务被分配到不同的队列,这些队列有些干活干的块,有些干得慢。于是干的快的,一看自己没任务需要执行了,就去隔壁的队列里面拿去任务执行

ForkJoinTask
ForkJoinTask就是ForkJoinPool里面的每一个任务。他主要有两个子类:RecursiveAction和RecursiveTask。然后通过fork()方法去分配任务执行任务,通过join()方法汇总任务结果。

测试代码

①RecursiveTask :有返回结果

第一步:创建MyRecursiveTask子类在ForkJoinUtil中

package com.hospital.hospital.utils;

import java.util.concurrent.RecursiveTask;
import java.util.stream.IntStream;

/**
 * @Author: dengcs
 * @Date: 2020/6/17 10:29
 * Comment:
 */
public class ForkJoinUtil {
	private final static int threshold =3;
	private final static AtomicInteger mySum = new AtomicInteger(0);
    private static class MyRecursiveTask extends RecursiveTask<Integer>{
        private final int start;
        private final int end;

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

        @Override
        protected Integer compute() {
            /*如果任务不能再分,则进行计算*/
            if(end - start <= threshold){
                return IntStream.rangeClosed(start,end).sum();
            }else {
                int mid = (start + end) / 2;
                MyRecursiveTask leftTask = new MyRecursiveTask(start,mid);
                MyRecursiveTask rightTask = new MyRecursiveTask(mid+1,end);
                leftTask.fork();
                rightTask.fork();
                return leftTask.join()+rightTask.join();
            }
        }
    }
}

在这个方法中传进去数据,然后使用二分法继续分配给子任务,当任务小的不能再分,那就汇总返回。

第二步在ForkJoinUtil的main函数中去测试

    public static void main(String[] args){
        final ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Integer> result = pool.submit(new MyRecursiveTask(0,100));
        try {
            System.out.println(result.get());
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }        
    }

在这个类中我们定义了一个阈值,然后创建一个ForkJoinPool,在这个池子中新建我们刚刚创建的Task任务,最终返回我们结果。

②RecursiveAction:无返回结果

第一步:创建MyNoResultRecursiveAction子类在ForkJoinUtil中

    //无返回结果
    private static class MyNoResultRecursiveAction extends RecursiveAction{
        private final int start;
        private final int end;

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

        @Override
        protected void compute() {
            /*如果任务不能再分,则进行计算*/
            if(end - start <= threshold){
                mySum.addAndGet(IntStream.rangeClosed(start,end).sum());
            }else {
                int mid = (start + end) / 2;
                MyNoResultRecursiveAction leftTask = new MyNoResultRecursiveAction(start,mid);
                MyNoResultRecursiveAction rightTask = new MyNoResultRecursiveAction(mid+1,end);
                leftTask.fork();
                rightTask.fork();
            }
        }
    }

在这个方法中我们不需要有return语句,和RecursiveTask类似。

(2)第二步在ForkJoinUtil的main函数中去测试

    public static void main(String[] args) throws InterruptedException {
        final ForkJoinPool pool = new ForkJoinPool();
        //无返回值,但可打印出结果
        pool.submit(new MyNoResultRecursiveAction(0,100));
        pool.awaitTermination(100, TimeUnit.MILLISECONDS);
        //打印
        System.out.println(mySum);
    }
}

总结

ForkJoinTask在执行的时候可能会抛出异常,在主线程中是无法直接获取的,但是可以通过ForkJoinTask提供的isCompletedAbnormally()方法来检查任务是否已经抛出异常或已经被取消了。
源码下载:https://download.csdn.net/download/weixin_39098944/12529350

你可能感兴趣的:(并发编程-分而治之框架ForkJoin)