目录
- 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的实现类,主要负责工作线程的管理、任务队列的维护,以及控制整个任务调度流程;
构造方法: 对外提供了三种构造方法:
- 无参构造
public ForkJoinPool() { this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),defaultForkJoinWorkerThreadFactory, null, false); }
- 一个int类型构造
public ForkJoinPool(int parallelism) { this(parallelism, defaultForkJoinWorkerThreadFactory, null, false); }
- 多个参数的构造,代码如下:
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. 执行流程
4.1 执行流程
下图为ForkJoinPool的整体执行流程,下文的源码分析会结合该图详细介绍,介绍完毕之后,最后补充具体的方法级别调用图
4.2 任务窃取
待添加
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(); }
补充
来源:oschina
链接:https://my.oschina.net/lovexin/blog/3131451