ForkJoinPool源码解析

*爱你&永不变心* 提交于 2019-12-05 00:30:58

目录

  1. ForkJoinPool介绍
    • 什么是ForkJoinPool?
    • 相对ThreadPoolExecutor有什么优缺点?
    • 什么时候使用ForkJoinPool?
  2. 实例DEMO
  3. 涉及核心类或组件
    • ForkJoinPool
    • ForkJoinTask
    • ForkJoinWorkerThread
    • WorkQueue
  4. 执行流程
  5. 源码分析
    • 添加任务
    • fork任务
    • join任务
  6. 补充部分

详情

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的实现类,主要负责工作线程的管理、任务队列的维护,以及控制整个任务调度流程;

构造方法: 对外提供了三种构造方法:

  1. 无参构造
  2. 一个int类型构造(该参数是设置并行度的)
  3. 多个参数的构造,代码如下:
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();
}

补充

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!