java并发之Fork/Join框架

来源:互联网 发布:制作十字绣软件 编辑:程序博客网 时间:2024/04/28 03:45

1.什么是Fork/Join框架

Fork/Join框架是用来提供并行执行的框架,特点就是把一个大任务分成若干个小任务来执行,最后把那些小任务的结果汇总成整个任务的结果。

2.工作窃取算法的介绍

工作窃取算法的作用就是指某个线程从其他队列里窃取任务来执行。原理:就是如果我们有一个大的任务需要执行,先会把大任务分割成若干个子任务A,BC,D,E.....。然后将子任务分别放到不同的队列中,如A线程放入到A队列,B线程放到B队列中等。如果有的队列里的任务被子线程先执行完了,此时就处于等待了。别的队列里的任务还没执行完,那么就去别的队列里窃取一个任务来执行,从而充分利用了并行计算。

为了防止窃取任务的线程和被窃取任务线程之间的竞争。会使用双端队列,被窃取任务的线程从双端队列的头部取任务执行,而窃取任务的线程从双端队列的尾部取任务执行。

            Fork/Join运行流程图:

                                                   

3.Fork/Join框架的设计

步骤1:分割任务:

使用ForkJoinTask的子类RecursiveAction(用于没有返回结果的任务)和RecursiveTask(用于有返回结果的任务)的fork();方法来分割任务。

步骤2:执行任务并合并结果:

使用ForkJoinTask的子类RecursiveAction(用于没有返回结果的任务)和RecursiveTask(用于有返回结果的任务)的join();方法来将结果合并。(以上任务ForkJoinTask的执行都需要使用ForkJoinPool来执行)

任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。

4.Fork/Join框架的实现原理

ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成。而过程就是ForkJoinTask数组负责存放ForkJoinPool的任务,而ForkJoinWorkerThread数组负责执行任务。

ForkJoinTask的fork();方法实现原理

查看源码:


解释:如果我们调用了fork();方法,那么就会从ForkJoinWorkerThread中的当前线程来异步执行pushTash();方法。

而pushTash();方法是如何实现的?

查看源码:


解释:pushTask的策略就是将当前任务存放在ForkJoinTask数组队列里。然后使用signalWork();方法来唤醒或创建一个任务来执让当前线程执行。

ForkJoinTask的join();方法的实现原理

join();方法的作用就是阻塞当前线程并等待获取结果。

查看源码:




解释:通过源码可以看出方法主要是通过判断当前线程的状态来返回不同的结果。而任务的状态分为:已完成(NORMAL),被取消(CANCELLED),信号(SIGNAL)和出现异常(EXCEPTIONAL)。如果任务处于已完成状态,则直接返回任务结果。如果任务处于被取消的状态,则直接抛出CancellationException();异常。如果任务抛出异常,则直接抛出对应的异常即可。

细看源码,发现join判断当前线程处于什么状态是通过doJoin();方法来判断的,进而做不同处理。

查看doJoin();方法的源码:


解释:先从ForkJoinWorkerThread数组中创建一个任务作为当前线程。如果当前任务完成了,那么直接返回当前任务的状态码s。如果没有执行完,则从任务数组里取出任务并执行。如果任务顺利执行完成,则就状态标记为NORMAL。如果出现异常则记录状态,并将任务状态标记为EXCEPTIONAL。

5.Fork/Join框架的使用

实现1+2+3......+10的结果,使用ForkJoin框架来分割计算,合并结果。

public class CalculateTask extends RecursiveTask<Integer>{        private static final long serialVersionUID = 1L;    //任务是否分割的判断标准    private static final int forkValue=2;    private int start;    private int end;        public CalculateTask(int start, int end) {        this.start = start;        this.end = end;    }    @Override    protected Integer compute() {        //用来统计和        int sum=0;        //用来判断任务是否需要分割        boolean isFork=(end-start)<=forkValue;        if(isFork){            for(int i=start;i<=end;i++){                sum+=i;            }        }else{            int middle =(start+end)/2;            //创建两个子任务,以middle处分开两个加法任务            CalculateTask leftTask=new CalculateTask(start,middle);            CalculateTask rightTask=new CalculateTask(middle+1,end);            leftTask.fork();            rightTask.fork();            //得到两个子任务的执行的计算结果            int leftResult=leftTask.join();            int rightResult=rightTask.join();            sum=leftResult+rightResult;        }        return sum;    }    public static void main(String[] args) {        ForkJoinPool forkJoinPool=new ForkJoinPool();        CalculateTask calculateTask=new CalculateTask(1,10);        Future<Integer> sumResult=forkJoinPool.submit(calculateTask);        try {            System.out.println(sumResult.get());        } catch (Exception e) {            throw new RuntimeException(e);        }    }}
结果:

55

解释:由于我们的计算是有返回结果的,而且需要使用返回结果RecursiveTask类,所以继承这个类,实现compute();抽象方法。然后将整个加法任务分为若干个子加法任务,标准就是两个数之间的距离大于2。

6.Fork/Join框架的异常处理

有时候我们使用ForkJoinTask会报出异常,如线程中断等异常。然而在main中是无法直接捕获异常的,就需要对任务进行检查来判断是否已经抛出了异常或被取消了。ForkJoinTask提供了isCompletedAbnormally();方法来检查。

形式:


解释:如果任务被取消则返回CancellationException异常。如果任务没有完成或者没有抛出异常则返回null。




0 0