前言
ForkJoinPool是一种线程池,它和其他线程池的不同之处在于,它主要体现的是分治思想,和之前提到的LongAdder的原理有点相似,就是把一个大任务拆分成很多小任务去处理,最后再对所有执行结果进行汇总,这样就可以使用多线程技术来提高效率。
原理
ForkJoinPool之所以高效还有一个原因是它采用的是工作窃取(work-stealing)的模式,ThreadPoolExecutor线程池的实现是多个线程共享一个任务队列,而工作窃取模式每个线程都有一个双端队列Deque,当一个线程队列中的任务执行完之后,会去其他线程的队列末尾取任务执行,工作窃取模式主要有以下几个特点
- 每个线程都有一个双端队列
- 每个线程执行完自己队列中的任务之后,会去别的线程的队列末尾窃取任务执行,充分利用
CPU资源 - 从尾部窃取任务可以有效降低线程之间的竞争问题
- 窃取的任务队列中只有一个任务时,会通过
CAS方式同原线程进行竞争
用法
下面通过一个数字求和功能来介绍下ForkJoinPool的用法
// 自定义类继承任务类
public class MyTask extends RecursiveTask<Integer> {
private int n;
public MyTask(int n) {
this.n = n;
}
@Override
protected Integer compute() {
if (n == 1) { // 拆分终止条件
return 1;
}
MyTask myTask = new MyTask(n - 1); // 创建子任务
myTask.fork(); // 拆分任务
return n + myTask.join(); // join等待返回结果
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool(); // 创建线程池
Integer result = forkJoinPool.invoke(new MyTask(5)); // 执行任务
System.out.println(result);
}
}
ForkJoinPool执行的任务类型是ForkJoinTask,它有两个子类分别是没有返回值的RecursiveAction和有返回值的RecursiveTask,根据实际业务需要来决定使用哪个类。
自定义任务类继承这两个任务类,然后重写compute方法来拆分任务,其实和递归的做法很相似,上面是求数字1~n的和,首先有一个终止拆分的条件就是n=1了,然后task(n)可以把任务拆分成n和task(n-1),task(n-1)又可以拆分成n-1和task(n-2)......依次类推进行拆分。fork的作用就是拆分任务,而join的作用就是等待任务结束返回结果。
应用
在Java8中的并行流(parallelStream)和 CompletableFuture,都是基于ForkJoinPool实现的。