forkjoin最初出现在java7中,在java8的demo里依然可以看到forkjoin的身影。
如果用简单的话来描述forkjoin,就是拆解子任务,并发运行,并最后统一运行结果。
fork:叉
join:连接
先分叉再连接,大概就是这么个意思。
forkjoin适用于如下场景:
如果有一个很大的任务需要被执行多次才能完成,而这个大任务可以以递归的方式划分为若干个独立的小任务,
而这些小任务的运行结果最终能通过某种方式汇合计算出大任务的结果。
如果有以上场景的话,就适用于forkjoin框架。
举个最简单的例子,求1-100的和。这个任务非常适合拆解,我可以把它拆成若个数字区间段,比如1-10,11-20...91-100。
然后每一个区间段分开运行,最终汇总每个区间段的和,加起来就是总和。
那么其实这个问题,我们就算不利用java中提供的forkjoin也可以实现,大概的代码就是利用Futrue和Callable来完成。
这里我就不写了,因为主要想讲的是forkjoin框架。
这里简单介绍java中提供的三个类
1. abstract class java.util.concurrent.ForkJoinTask<V>
2. abstract class java.util.concurrent.RecursiveAction extends ForkJoinTask<Void>
需要实现的抽象方法:protected abstract void compute();
3. abstract class java.util.concurrent.RecursiveTask<V> extends ForkJoinTask<V>
需要实现的抽象方法: protected abstract V compute();
ForkJoinTask是实现forkjoin的核心功能的抽象类。包括管理线程池,实例化线程运行子任务这些工作都在这个类里完成。
RecursiveAction ,我们一般直接继承的类,父类是ForkJoinTask,提供抽象方法void compute()供我们实现。主要处理无需返回值的子任务拆解场景。
RecursiveTask,我们一般直接继承的类,父类是ForkJoinTask,提供抽象方法V compute()供我们实现,可以返回泛型指定的数据对象,处理有返回的任务拆解场景。
那么一般我们如何实现compute呢?
首先要定义递归算法的临界值,也就是当条件不满足或低于临界值时,就不再拆解子任务,而是直接运算结果。
然后,在满足可以拆解的情况下,我们就拆解任务。
那么一次可以拆解几个任务呢?理论上拆解几个都行,但是要考虑到每个任务都是一个线程,所以拆解任务的粒度不宜过小,当然为了提高并发效率,也不宜过大,适中就好。
继续回到我们的问题上来,之前我们的举例是讲计算1-100的总和这个任务拆解成若干个子任务。
那么我们可以做一个简单的拆解,比如每次求和时,如果求和的起始和终止数字的差值大于20,就拆解
拆解的方案是从中间拆解。比如 1-100求和,差值>20,因此拆解为1-50求和和51-100求和两个子任务。
子任务运行时,如果发现差值>20,则继续拆解,直到满足差值<=20的情况,则调用求和算法求和。
然后将子任务的统计结果循环相加,最终得到1-100的和。
现在开始编码
public class ForkJoinTaskTest {
private static AtomicInteger seq = new AtomicInteger(0);
private static Object lock = new Object();
private static final class Sum extends RecursiveTask<Integer> {
private static final int THRESHOLD = 20;
int start;
int end;
String name;
int level;
int id;
int parentId;
String parentName;
public Sum(int id, Sum parent, String name, int start, int end, int leve