11 分之合并
11.1 什么是ForkJoin
ForkJoin是一个并发执行任务的框架,其核心思想就是任务切分,结果合并。
当任务足够小的时候可以直接解决它,否则将任务切分成可以独立解决的部分,小的任务解决之后再将它们合并。
ForkJoin框架得到组成
- ForkJoinTask
- ForkJoinWorkerThread
- ForkJoinPool
ForkJoin框架的特点
- 工作窃取
一些线程共同执行某个大任务的时候,已完成本线程工作的线程可以窃取其他线程未完成的任务,提高执行效率
11.2ForkJoinTask
该类是ForkJoinPool执行的任务,要使用ForkJoin框架,必须创建ForkJoinTask任务。该类提供了将任务分割成子任务的方法fork、等待子任务完成的方法join,通常情况下,我们将一个大的任务fork成两个小的任务,在通过join等待子任务完成合并结果。
ForkJoinTask是一个抽象类,它有两个子类:RecursiveTask和RecursiveAction,这两个类也是抽象类,其中RecursiveTask是带有返回值得到任务,RecursiveAction是没有返回值的任务。Recursive是递归的意思,从字面上便
可看出这些任务是可以分割成子任务递归完成的
ForkJoinTask实现了Future接口,说明它是一个可以异步获取结果的任务,同时该任务实现了序列化接口。
11.3 ForkJoinWorkerThread和工作窃取
该类是执行ForkJoinTask的线程,ForkJoin框架使用了线程维护一个双端队列,并实现了工作窃取(work-stealing)算法来提高并发效率。
线程每次从所维护队列的头部取任务执行,当队列中没有任务可执行时,该线程会去其他线程维护队列的尾部窃取任务执行。换句话说,对于本线程维护的队列,任务执行的顺序是LIFO(后进先出),窃取其他线程队列任务的执行顺序是FIFO(先进先出)。看下工作窃取示意图:
11.4 ForkJoinPool
ForkJoinPool是执行ForkJoinTask的线程池,它在内部维护了一个ForkJoinThread数组,这些线程就是ForkJoinPool管理的线程。类似于ThreadPoolExecutor,我们将任务提交到ForkJoinPool之后,它维护的线程就会执行我们提交的任务。不同的是,ForkJoinPool会通过工作窃取算法,让线程在完成本身任务之后帮其他线程执行任务, 这个调度是ForkJoinPool的核心功能。
11.5 如何使用ForkJoin
- 构建任务:继承ForkJoinTask或者其子类RecursiveTask
- 通过ForkJoinPool.execute(new 任务)
创建任务Task
/**
* @author yangxj
* @description 描述:递归任务,泛型是计算后返回的结果类型
* @date 2020/2/24 15:56
*/
public class MyRecursiveTask extends RecursiveTask<Long> {
private long start; //开始值
private long end; //结束值
private static final long temp = 10000L; //中间值
public MyRecursiveTask(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start <= temp) {
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
}else{
//获取中间值
long middle = (start + end)/2;
/**
* fork()会不断的循环
*/
//第一个任务
MyRecursiveTask rightTask = new MyRecursiveTask(start,middle);
rightTask.fork();
//第二个任务
MyRecursiveTask leftTask = new MyRecursiveTask(middle+1, end);
leftTask.fork();
//合并结果
return rightTask.join() + leftTask.join();
}
}
}
执行任务
public class ForkJoinTest {
private static final long SUM = 20_0000_0000;
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
test2();
test3();
}
/**
* 使用普通方法
*/
public static void test1() {
long star = System.currentTimeMillis();
long sum = 0L;
for (long i = 1; i < SUM ; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println(sum);
System.out.println("时间:" + (end - star));
System.out.println("----------------------");
}
/**
* 使用ForkJoin 方法
*/
public static void test2() throws ExecutionException, InterruptedException {
long star = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new MyRecursiveTask(0L, SUM);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long along = submit.get();
System.out.println(along);
long end = System.currentTimeMillis();
System.out.println("时间:" + (end - star));
System.out.println("-----------");
}
/**
* 使用 Stream 流计算
*/
public static void test3() {
long star = System.currentTimeMillis();
long sum = LongStream.range(0L, 20_0000_0000L).parallel().reduce(0, Long::sum);
System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println("时间:" + (end - star));
System.out.println("-----------");
}
}
详细内容参考ForkJoin框架