目录
- ForkJoinPool介绍
- 什么是ForkJoinPool?
- 相对ThreadPoolExecutor有什么优缺点?
- 什么时候使用ForkJoinPool?
- 实例DEMO
- 涉及核心类或组件
- ForkJoinPool
- ForkJoinTask
- ForkJoinWorkerThread
- WorkQueue
- 执行流程
- 源码分析
- 添加任务
- fork任务
- join任务
- 补充部分
详情
1. ForkJoinPool介绍
1.1 什么是ForkJoinPool?
ForkJoinPool是在JDK1.7新添加的一个JUC线程池工具类,它同样继承了AbstractExcutorService,如下图所示
在未开始介绍该类之前,我们先从字面意思猜想一下该类的左右:fork:分叉;join:参加、联合、连接;pool:小池;翻译:参加分叉的小池,或者连接分叉的小池。恩,,,这个翻译坑爹呀!!!!

不不不,不是这样的,咱们可以适当的加点猜想嘛,比如说在JUC中,一定与并发有关系,那么可否这么翻译呢?
“一个将任务拆分并结果合并的线程池”。
1.2 相对ThreadPoolExecutor有什么优缺点?
从上文我们已经知道ForkJoinPool是一个线程池,那么他与咱们常用的 ThreadPoolExecutor 线程池有什么区别呢? 首先看下两个线程池的结构图:
接着我们分析下:
ThreadPoolExecutor 对于任务会先交给核心线程去执行,如果核心线程数不够则会放入队列,线程池中的线程统一去任务队列中获取任务并执行,我们可以从这样的设计中很明显的看出来:ThreadPoolExecutor适合任务量不多,执行时间不确定的任务类型,比如说:网络IO操作、定时任务等
ForkJoinPool 则是一个线程对应一个任务队列,每个任务正常来说只需要执行自己的任务;如果线程对应的任务列表为空的时候,会在随机窃取其他线程任务列表中的任务(为减少获取任务竞争,会从任务另一端获取任务),当然下图没有表示出来,这样既减少了任务竞争,又能充分利用CPU资源。我们可以从这样的设计中很明显的看出来:ForkJoinPool非常适合执行任务比较多、执行事件比较短的程序,比如过滤集合中的元素(JDK1.8 stream底层就是ForkJoinPool哟)
最后补充: ForkJoinPool 的出现不是为了替换ThreadPoolExecutor这一类的线程池,而是为了做功能上的补充,两者各有使用场景,根据不同的场景使用不同的线程池即可
1.3 什么时候使用ForkJoinPool?
- 引用 1.2 中ForkJoinPool 分析和补充部分:ForkJoinPool非常适合执行任务比较多、执行事件比较短的程序,比如过滤集合中的元素(JDK1.8 stream底层就是ForkJoinPool哟);
- ForkJoinPool 的出现不是为了替换ThreadPoolExecutor这一类的线程池,而是为了做功能上的补充,两者各有使用场景,根据不同的场景使用不同的线程池即可。
2 实例DEMO
接下里这里就以过滤为本中违禁字为例(现实中肯定使用违禁词了,本文只是做简单DEMO),先说下具体的测试方法:这里有一串文本和已知的违禁字集合,如果文本中的文字在违禁字集合中,则计算违禁字出现的次数并返回
// 测试方法
public class ForkJoinPoolLearn {
public final static String CONTENT = "哇,好帅哟!哇,是啊,我好喜欢呢!哇,可否给个签名呢?";
public static final int THRESHHOLD = 5;
public static List<String> BLACK_WORDS = new ArrayList<>();
static {
BLACK_WORDS.add("哇");
}
public static void main(String[] args) {
//使用ForkJoinPool来执行任务
// 有返回值对象
System.out.println("即将测试有返回值对象。。。");
ForkJoinPool forkJoinPool = new ForkJoinPool();
MyRecursiveTask myRecursiveTask = new MyRecursiveTask(0, ForkJoinPoolLearn.CONTENT.length(), Arrays.asList(ForkJoinPoolLearn.CONTENT.split("")));
Integer value = forkJoinPool.invoke(myRecursiveTask);
System.out.println(String.format("字符串:%s 中包含违禁词数量:%s,违禁词:%s", CONTENT, value, StringUtils.join(BLACK_WORDS, ",")));
}
}
// 提交任务类
public class MyRecursiveTask extends RecursiveTask<Integer> {
private int startIndex;
private int endIndex;
private List<String> words;
public MyRecursiveTask(int startIndex, int endIndex, List<String> words) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.words = words;
}
@Override
protected Integer compute() {
int sum = 0;
if ((endIndex - startIndex) <= ForkJoinPoolLearn.THRESHHOLD) {// 如果长度不可再分割,则开始做过滤
for (int i = startIndex; i < words.size() && i < endIndex; i++) {
String word = words.get(i);
if (ForkJoinPoolLearn.BLACK_WORDS.contains(word)) {
sum += 1;
}
}
} else {// 如果长度过长,fork为两个任务来处理
int middle = (startIndex + endIndex) / 2;
MyRecursiveTask left = new MyRecursiveTask(startIndex, middle, words);
MyRecursiveTask right = new MyRecursiveTask(middle, endIndex, words);
left.fork();
right.fork();
Integer leftValue = left.join();
Integer rightValue = right.join();
sum = leftValue + rightValue;
}
return sum;// 返回计算后的值
}
}
以上示例返回的结果:“字符串:哇,好帅哟!哇,是啊,我好喜欢呢!哇,可否给个签名呢? 中包含违禁词数量:3,违禁词:哇”
3. 涉及核心类或组件
3.1 ForkJoinPool
该类为ExecutorService的实现类,主要负责工作线程的管理、任务队列的维护,以及控制整个任务调度流程;
构造方法: 对外提供了三种构造方法:
- 无参构造
- 一个int类型构造(该参数是设置并行度的)
- 多个参数的构造,代码如下:
public ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
boolean asyncMode) {
this(checkParallelism(parallelism),checkFactory(factory),handler,asyncMode ? FIFO_QUEUE : LIFO_QUEUE,"ForkJoinPool-" + nextPoolId() + "-worker-");
checkPermission();
}
其实以上三个构造方法最终都是调用的下面的私有构造方法:
private ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
int mode,
String workerNamePrefix) {
this.workerNamePrefix = workerNamePrefix;
this.factory = factory;
this.ueh = handler;
this.config = (parallelism & SMASK) | mode;
long np = (long)(-parallelism); // offset ctl counts
this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
}
解释一下每个参数的含义:
parallelism:并行度,目前在这里暂且解释为线程池的线程数(但这个说法并不完全正确,再看下文会具体解释其含义)
factory:创建 ForkJoinWorkerThread 的工厂接口
handler:异常处理器
mode:取任务的时候是是FIFO还是LIFO模式,0:LIFO;1:FIFO;
workerNamePrefix:ForkJoinWorkerThread的名称
ctl:线程池的核心控制字段
提交任务方式
submit: 有返回值,可提交Runnable,Callable,ForkJoinTask类型任务
invoke: 有返回值,可提交ForkJoinTask类型任务,根据不同的 ForkJoinTask 自动判断是否有返回值
execute: 无返回值,可提交Runnable,ForkJoinTask类型任务
由于该类实现过于复杂,这里类结构图就不在这里粘贴了,小伙伴可自行打开IDE研究
3.2 ForkJoinTask
该类为Future接口的实现类,fork是其核心方法,用于分解任务并异步执行;而join方法在任务结果计算完毕之后才会运行,用来合并或返回计算结果;其下两个常用实现类:
- RecursiveAction:表示没有返回结果的ForkJoin任务
- RecursiveTask:表示具有返回结果的ForkJoin任务,示例DEMO中就是使用该类来提交任务
3.3 ForkJoinWorkerThread
该类为Thread的子类,作为线程池中的工作线程(Worker)执行任务;
线程池中的每个工作线程(ForkJoinWorkerThread)内部都维护一个自己的任务队列(WorkQueue),工作线程优先处理自身队列中的任务(LIFO或FIFO顺序,由线程池构造时的参数 mode 决定),自身队列为空时,以FIFO的顺序随机窃取其它队列中的任务。
3.4 WorkQueue
该类为ForkJoinPool的内部类,用于保存任务;
4. 执行流程
下图为ForkJoinPool的整体执行流程,下文的源码分析会结合该图详细介绍,介绍完毕之后,最后补充具体的方法级别调用图
5. 源码分析
到目前为止,大致的执行流程已经明白了,接下来了来具体讲讲源码来了解一下具体的执行过程
5.1 添加任务
待添加
5.2 fork任务
public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
// 当前线程就是 ForkJoinWorkerThread,那么自己fork的任务,自己执行咯。正所谓,自己挖的坑哭着也要填完
((ForkJoinWorkerThread)t).workQueue.push(this);
else
// 没人管的任务,那就统一添加到任务队列,最终随机找个线程去执行
ForkJoinPool.common.externalPush(this);
return this;
}
5.3 join任务
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
// 出错之后,处理下错误
reportException(s);
// 参考添加任务部分
return getRawResult();
}