概念
ForkJoin是由JDK1.7后提供多线并发处理框架。ForkJoinPool由Java大师Doug Lea主持编写,处理逻辑大概分为两步。
1.任务分割:Fork(分岔),先把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割。
2.合并结果:join,分割后的子任务被多个线程执行后,再合并结果,得到最终的完整输出。
组成
ForkJoinTask:主要提供fork和join两个方法用于任务拆分与合并;多数使用RecursiveAction(无返回值的任务)和RecursiveTask(需要返回值)来实现compute方法
ForkJoinPool:调度ForkJoinTask的线程池
ForkJoinWorkerThread:Thread的子类,存放于线程池中的工作线程(Worker);
WorkQueue:任务队列,用于保存任务;
基本使用
一个典型的例子:计算1-1000的和
package com.wangj;
import java.util.concurrent.*;
public class SumTask {
private static final Integer MAX = 100;
static class SubTask extends RecursiveTask<Integer> {
// 子任务开始计算的值
private Integer start;
// 子任务结束计算的值
private Integer end;
public SubTask(Integer start, Integer end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start < MAX) {
//小于边界,开始计算
System.out.println("start = " + start + ";end = " + end);
Integer totalValue = 0;
for (int index = this.start; index <= this.end; index++) {
totalValue += index;
}
return totalValue;
} else {
//否则,中间劈开继续拆分
SubTask subTask1 = new SubTask(start, (start + end) / 2);
subTask1.fork();
SubTask subTask2 = new SubTask((start + end) / 2 + 1, end);
subTask2.fork();
return subTask1.join() + subTask2.join();
}
}
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
Future<Integer> taskFuture = pool.submit(new SubTask(1, 1000));
try {
Integer result = taskFuture.get();
System.out.println("result = " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace(System.out);
}
}
}
设计思想
普通线程池内部有两个重要集合:工作线程集合,和任务队列。
ForkJoinPool也类似,工作集合里放的是特殊线程ForkJoinWorkerThread,任务队列里放的是特殊任务ForkJoinTask
不同之处在于,普通线程池只有一个队列。而ForkJoinPool的工作线程ForkJoinWorkerThread每个线程内都绑定一个双端队列。
在fork的时候,也就是任务拆分,将拆分的task会被当前线程放到自己的队列中。
队列中的任务被线程执行时,有两种模式,默认是同步模式(asyncMode==false)从队尾取任务(LIFO)
窃取:当自己队列中执行完后,工作线程会到其他队列的队首获取任务(FIFO),取到后如果任务再次fork,拆分会被放入当前线程的队列,依次扩张
注意点
使用ForkJoin将相同的计算任务通过多线程执行。但是在使用中需要注意:
1、注意任务切分的粒度,也就是fork的界限。并非越小越好
2、判断要不要使用ForkJoin。任务量不是太大的话,串行可能优于并行。因为多线程会涉及到上下文的切换