1.1 什么是ForkJoin
从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务,就是将一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果。
过程主要有两步:
- 第一: 任务切割;
- 第二 : 结果合并
1.2 ForkJoin特点
工作窃取(work-stealing): 是指某个线程从其他队列里窃取任务来执行。
就是一个工作线程下会维护一个包含多个子任务的双端队列。而对于每个工作线程来说,会从头部到尾部依次执行任务。这时,总会有一些线程执行的速度较快,很快就把所有任务消耗完了。
线程的任务窃取: 比如你和你的小伙伴在一起吃水果,你的那份吃完了,他那份没吃完,这个时候你偷偷的拿了他的一些水果吃了。存在执行2个任务的子线程,存在A,B两个WorkQueue在执行任务,A的任务执行完了,B的任务没执行完,那么A的WorkQueue就从B的WorkQueue的ForkJoinTask数组中拿走了一部分尾部的任务来执行,可以合理的提高运行和计算效率。
1.3 核心类
1.3.1 ForkJoinPool
WorkQueue是一个ForkJoinPool中的内部类,它是线程池中线程的工作队列的一个封装,支持任务窃取。
每个线程都有一个WorkQueue,而WorkQueue中有执行任务的线程(ForkJoinWorkerThreadowner),还有这个线程需要处理的任务(ForkJoinTask<?>[] array)。那么这个新提交的任务就是加到array中。
1.3.2 ForkJoinTask
ForkJoinTask代表运行在ForkJoinPool中的任务。
主要方法:
- fork() 在当前线程运行的线程池中安排一个异步执行。简单的理解就是再创建一个子任务。
- join() 当任务完成的时候返回计算结果。
- invoke() 开始执行任务,如果必要,等待计算完成。
子类: Recursive :递归
- RecursiveAction 一个递归无结果的ForkJoinTask(没有返回值)
- RecursiveTask 一个递归有结果的ForkJoinTask(有返回值)
10.4 代码示例
10.4.1 源码分析
10.4.2 代码验证
核心代码
package cn.guardwhy.forkJoin;
import java.util.concurrent.RecursiveTask;
/*
* 求和计算的任务!!
* 1.使用forkjoin
* 1.1forkjoinpool 通过它来执行。
* 1.2 计算任务ForkJoinPool.excute(ForkJoinTask task)
* 1.3 计算类要继承ForkJoinTask
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start; // 起始值
private Long end; // 结束值
// 临界值
private Long temp = 10000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
// 计算方法
@Override
protected Long compute() {
// 常规方式
if((end - start) < temp){
// 定义sum值
Long sum = 0L;
for (Long i = start; i <= end ; i++) {
sum += i;
}
return sum;
}else {
// ForkJoin递归
long middle = (start + end) / 2; // 中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
// 拆分任务,把任务压入线程队列
task1.fork();
ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
// 拆分任务,把任务压入线程队列
task2.fork();
return task1.join() + task2.join();
}
}
}
测试代码
package cn.guardwhy.forkJoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
public class ForkJoinTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1(); // sum=500000000500000000 时间: 6235
test2(); // sum=500000000500000000 时间: 6017
// test03(); // sum=500000000500000000 时间: 102
}
// 1.普通方式
public static void test1() {
Long sum = 0L;
long start = System.currentTimeMillis();
for (Long i = 1L; i <=10_0000_0000 ; i++) {
sum += i;
}
Long end = System.currentTimeMillis();
System.out.println("sum=" +sum+" 时间: " + (end-start));
}
// 2.使用ForkJoin方法
private static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
// 提交任务
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum=" +sum+" 时间: " + (end-start));
}
// 3.链式编程
private static void test03() {
long start = System.currentTimeMillis();
// Stream并行流
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum=" +sum+" 时间: " + (end-start));
}
}