开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第23天,点击查看活动详情
思想:分而治之。将一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果。
举例说明
1、我们举个例子:如果要计算一个超大数组的和,最简单的做法是用一个循环在一个线程内完成:
2、还有一种方法,可以把数组拆成两部分,分别计算,最后加起来就是最终结果,这样可以用两个线程并行执行:
3、如果拆成两部分还是很大,我们还可以继续拆,用4个线程并行执行:
这就是Fork/Join任务的原理:判断一个任务是否足够小。如果是,直接计算,否则,就分拆成几个小任务分别计算。这个过程可以反复“裂变”成一系列小任务。
编码实现
整个任务流程如下所示:
- 首先继承任务,覆写任务的执行方法
- 通过判断阈值,判断该线程是否可以执行
- 如果不能执行,则将任务继续递归分配,利用fork方法,并行执行
- 如果是有返回值的,才需要调用join方法,汇集数据。
主要的两个类:
RecursiveAction一个递归无结果的ForkJoinTask(没有返回值)RecursiveTask一个递归有结果的ForkJoinTask(有返回值)
RecursiveTask
public class ForkJoinRecursiveTask {
/*
1、分到哪种程度可以不用分了
2、也就是设置一个任务处理最大的阈值
*/
private final static int MAX_THRESHOLD = 3;
public static void main(String[] args) {
final ForkJoinPool joinPool = new ForkJoinPool();
ForkJoinTask<Integer> future = joinPool.submit(new CalculatedRecursiveTask(0, 1000));
try {
Integer integer = future.get();
System.out.println("执行结果:" + integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
private static class CalculatedRecursiveTask extends RecursiveTask<Integer> {
private final int start;//任务开始的上标
private final int end;//任务开始的下标
private CalculatedRecursiveTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= MAX_THRESHOLD) {//如果自己能处理,就自己计算
return IntStream.rangeClosed(start, end).sum();
} else {//自己处理不了,拆分任务
int middle = (end + start) / 2;
CalculatedRecursiveTask leftTask = new CalculatedRecursiveTask(start, middle);
CalculatedRecursiveTask rightTask = new CalculatedRecursiveTask(middle + 1, end);
//分别去执行
leftTask.fork();
rightTask.fork();
//把返回结果合起来
return leftTask.join() + rightTask.join();
}
}
}
}
这个是有返回值的
RecursiveAction
这个是没有返回值的
public class ForkJoinRecursiveAction {
private final static int MAX_THRESHOLD = 3;//设置一个任务处理最大的阈值
private final static AtomicInteger SUM = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.submit(new CalculateRecursiveAction(0,1000));
//任务执行需要事件,这里可以等一下
forkJoinPool.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("执行结果为:" + SUM);
}
private static class CalculateRecursiveAction extends RecursiveAction{
private final int start;
private final int end;
private CalculateRecursiveAction(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if ((end-start)<=MAX_THRESHOLD){
SUM.addAndGet(IntStream.rangeClosed(start,end).sum());
}else {
int middle = (start+end)/2;
CalculateRecursiveAction leftAction = new CalculateRecursiveAction(start,middle);
CalculateRecursiveAction rightAction = new CalculateRecursiveAction(middle+1,end);
leftAction.fork();
rightAction.fork();
}
}
}
}
原理
整个流程需要三个类完成:
1、ForkJoinPool
- 既然任务是被逐渐的细化的,那就需要把这些任务存在一个池子里面,这个池子就是ForkJoinPool。
- 它与其它的ExecutorService区别主要在于它使用**”工作窃取”**,那什么是工作窃取呢?
- 工作窃取:一个大任务会被划分成无数个小任务,这些任务被分配到不同的队列,这些队列有些干活干的块,有些干得慢。于是干得快的,一看自己没任务需要执行了,就去隔壁的队列里面拿去任务执行。
2、ForkJoinTask
ForkJoinTask就是ForkJoinPool里面的每一个任务。他主要有两个子类:RecursiveAction和RecursiveTask。然后通过fork()方法去分配任务执行任务,通过join()方法汇总任务结果。
小总结
- Fork/Join是一种基于“分治”的算法:通过分解任务,并行执行,最后合并结果得到最终结果。
- ForkJoinPool线程池可以把一个大任务分拆成小任务并行执行,任务类必须继承自RecursiveTask或RecursiveAction。
- 使用Fork/Join模式可以进行并行计算以提高效率。