ForkJoin框架
目录
开始之前,请死记:
我们使用ForkJoin框架时,我们的主要业务逻辑都是:继承RecursiveTask或者RecursiveAction,业务核心逻辑fork/join叉分合并写在compute()方法内,伪码如下:
class MyTask extends RecursiveTask<T>{
public T compute(){
if (problem is small)
directly solve problem
else
split problem into independent parts
fork new subtasks to solve each part
join all subtasks
compose result from subresults
/**如果任务体量小
* 就执行该任务
* 否则
* 叉分体量大的父任务,叉分成新的体量小的子任务
* 子任务fork提交待执行(fork提交:fork与compute的相互递归调用)
* 子任务执行结果进行join合并
* 返回合并结果给父任务
*/
/**
* 简要说明fork和compute是怎么相互递归调用的
*
* @1:fork提交新任务,新任务压入线程池管理的队列workQueue之中
* @2:新任务由线程池调度工作线程来执行,工作线程LIFO或FIFO机制来执行新任务
* @3:新任务执行时,会调用exec(){compute();}方法
* @4:我们自定义的compute(){newChildTask.fork();}方法内又会形成新任务进行fork
* @5:进行@1-->@4的递归处理,直至新任务足够体量小时不用叉分可直接执行
*/
}
}
ForkJoin简介
JAVA 1.7引入了ForkJoin框架,它是一个并发执行任务的框架。Fork/Join Pool采用优良的设计、代码实现和硬件原子操作机制等多种思路保证其执行性能。其中包括(但不限于):计算资源共享、高性能队列、避免伪共享、工作窃取机制等。Doug Lea关于ForkJoin的论文中给出了这样一段伪代码:
Result solve(Problem problem)
if (problem is small)
directly solve problem
else
split problem into independent parts
fork new subtasks to solve each part
join all subtasks
compose result from subresults
ForkJoin的基本思想:【分而治之】当任务足够小的时候直接解决它,否则将任务分割成可以独立解决的部分,小的任务解决后再将它们合并,得到大的任务结果。看下示例图,更加清晰的理解它:
注:ForkJoin是一个单机框架,类似的分布式的框架有Hadoop这类的,它们的计算模型是MapReduce,体现了和ForkJoin一样的思想-分而治之。
ForkJoin核心类
-
ForkJoinPool :线程池,实现了ExecutorService接口和工作窃取算法,用于线程调度与管理
-
ForkJoinTask :任务,提供了fork()方法和join()方法。通常不直接使用,而是使用以下子类:
RecursiveAction :无返回值的任务,通常用于只fork不join的情形
RecursiveTask :有返回值的任务,通常用于fork+join的情形
- ForkJoinWorkerThread : 是 ForkJoinPool 内的 worker thread,被线程池调度,用于执行 ForkJoinTask
- WorkQueue : 存放ForkJoinTask的队列,有若干个队列
总体来说:ForkJoin框架有雷同线程池Executor框架的设计思路,关于Executor线程池请参考我的博客Executor线程池原理。ForkJoinPool为管家,内部管理了一堆工作线程ForkJoinWorkerThread,和若干任务队列WorkQueue(WorkQueue中存放着待执行的一堆任务ForkJoinTask)。ForkJoinTask是抽象的父类,现实中,我们的程序一般都是继承它的子类RecursiveAction(不需要任务有返回值)或者RecursiveTask(需要利用任务的返回值)即可。
接下来我们主要介绍下,ForkJoinPool线程池、RecursiveTask有返回值任务、RecursiveAction无返回值任务
搞懂了RecursiveTask,就必然一定掌握了RecursiveAction无返回值任务。
ForkJoinPool线程池
在ForkJoinPool主类的注释说明中,有这样一句话:
A static commonPool() is available and appropriate for most applications. The common pool is used by any ForkJoinTask that is not explicitly submitted to a specified pool.
Using the common pool normally reduces resource usage (its threads are slowly reclaimed during periods of non-use, and reinstated upon subsequent use).
中文翻译:ForkJoinPool类有一个静态方法commonPool(),适用于绝大多数的应用系统场景。公共池适用于任何类型的任务,只要这个任务不是必须提交给特定的池。使用commonPool通常可以帮助应用程序中多种需要进行归并计算的任务共享计算资源,从而使后者发挥最大作用(ForkJoinPools中的工作线程在闲置时会被缓慢回收,并在随后需要使用时被恢复)。
而这种获取ForkJoinPools实例的方式,才是Doug Lea推荐的使用方式 。 例如:
ForkJoinPool fjp = ForkJoinPool.commonPool(); //获得线程公共池
Future<T> result = fjp.invoke(task); //公共池内提交一个任务并等待执行结果
在JDK8当中,对ForkJoin进行了增强,可以将上面两句编写成一句代码即可:
Future<T> result = task.invoke(); //等价于上面两句代码
RecursiveTask有返回值任务
/**
* 截取的部分源码:
*
* compute()方法是核心业务逻辑,我们会在此处编写fork/join的叉分/合并递归的逻辑
*
* 之所以能递归,是因为执行任务时会调用exec(){result = compute();}方法
* 而我们在compute()方法中会编写叉分大任务为小任务,然后fork();而fork()方法的内部会
* 把新叉分的子任务提交给线程池管理的workQueue,待工作线程执行,工作线程一旦执行该子
* 任务时,又会调用exec()方法,又会执行compute()方法,于是这样就构成了递归调用。
*
*递归总结:fork()和compute()递归调用【compute()中调用fork(),fork()中又调用exec(){compute();}】
*/
public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
private static final long serialVersionUID = 5232453952276485270L;
/**
* The result of the computation.
*/
V result;
/**
* The main computation performed by this task.
* @return the result of the computation
*/
protected abstract V compute();
public final V getRawResult() {
return result;
}
protected final void setRawResult(V value) {
result = value;
}
/**
* Implements execution conventions for RecursiveTask.
*/
protected final boolean exec() {
result = compute();
return true;
}
}
一般我们编写代码,都是继承RecursiveTask<T>类,实现compute()方法。在compute()方法内部,编写fork/join叉分/合并逻辑。举例如下:
/**
* 以下代码的compute()方法内部的fork/join逻辑,用伪码表示:就是Doug Lea大师的论文伪码
* Result solve(Problem problem)
* if (problem is small)
* directly solve problem
* else
* split problem into independent parts
* fork new subtasks to solve each part
* join all subtasks
* compose result from subresults
*/
class Sum extends java.util.concurrent.RecursiveTask<Integer> {
private static final int THRESHOLD = 20; //叉分临界值(假定每任务体量20个元素合适)
int id;
public Sum(int id) {
this.id = id;
}
@Override
protected Integer compute() {
int allsum = 0;
int currentVolume