ForkJoin框架是Java 7 提供的一个分治思想的基本框架,即把一个大任务分割成若干个小任务,最终汇总每一个任务结果后得到大任务结果。通过查阅API发现,ForkJoinPool
继承AbstractExecutorService
,实现了Executor
,ExecutorService
。ForkJoinPool
用来实现工作窃取 算法。
一、ForkJoinTask及其子类
通过查看API发现,ForkJoinTask
是一个实现了Future接口的抽象类,在实际开发中,不需要继承ForkJoinTask
,而是重写其子类的compute
方法即可,在compute
方法里实现任务的切分和结果的合并。
常用子类为:RecursiveAction
(无返回值), RecursiveTask
(自定义泛型返回值)
二、fork\join 源码解析
fork()方法源码解析:
public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
此方法将任务放入ForkJoinWorkerThread
线程的工作队列,异步执行,立即返回。
ForkJoinPool
的signalWork
方法唤醒或创建一个线程来执行这个任务的compute
方法,如果还是大于切分的阀值,则会继续切分然后调用fork添加任务直到任务不再可切分。
join() 方法源码解析:
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
阻塞当前线程,直到对应的子任务完成运行并返回执行结果。或者,如果这个子任务存在于当前线程的任务等待队列中,则取出这个子任务进行递归执行。
三、实例
以一个文件遍历为例,进行示范:
public class ForkJoinTest extends RecursiveAction {
private String FilePath;
private ForkJoinTest(String FilePath){
this.FilePath = FilePath;
}
@Override
protected void compute() {
List<ForkJoinTest> forkJoinTests = new LinkedList<>();
File Dir = new File(FilePath);
if(!Dir.exists()){
System.out.println("文件夹不存在");
}else if(Dir.isFile()){
System.out.println(Dir.getPath()+Dir.getName());
}else if(Dir.isDirectory()){
for (File file : Dir.listFiles()) {
forkJoinTests.add(new ForkJoinTest(file.getPath()));
}
}
if(!forkJoinTests.isEmpty()){
for(ForkJoinTest forkJoinTest : invokeAll(forkJoinTests)){
forkJoinTest.join();
}
}
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTest forkJoinTest = new ForkJoinTest("F:\\File");
long time_start = System.currentTimeMillis();
forkJoinPool.execute(forkJoinTest);
forkJoinTest.join();
long time_end = System.currentTimeMillis();
System.out.println(time_end-time_start);
}
}
public static <T extends ForkJoinTask<?>> Collection<T> invokeAll(Collection<T> tasks)
:指定集合中的所有任务,当每个任务保持isDone或遇到一个(未检查)异常时返回,在这种情况下,异常被重新引导。 如果有多个任务遇到异常,则该方法会抛出任何一个异常。 如果任何一个任务遇到异常,其他任务可能被取消。 但是,单独任务的执行状态在特殊返回时不能得到保证。
在fork/join模式中,我们在子任务中常常使用fork方法来让子任务采取异步方法执行,但是这不是高效的实现方法,尤其是对于forkjoinPool在线程有限的情况下,子任务直接使用fork方法执行时间比使用invokeAll执行时间要长。
下面分析一下原理:对于fork/join模式,假如pool里面线程数量是固定的,那么调用子任务的fork方法相当于A先分工给B,然后A当监工不干活,B去完成A交代的任务。所以上面的模式相当于浪费了一个线程。那么如果使用invokeAll相当于A分工给B后,A和B都去完成工作。这样缩短了执行的时间。
摘自:https://blog.csdn.net/cxl0921/article/details/76460909