ForkJoin框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
我们再通过Fork和Join这两个单词来理解下Fork/Join框架,Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结果。比如计算1+2+。。+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和,最终汇总这10个子任务的结果。Fork/Join的运行流程图如下:
第一步分割任务。首先我们需要有一个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