ForkJoinPool及并行流解析

parallelStream原理。

parallelStream是并行流,依赖jdk1.7出现的Fork/Join框架。

Fork/Join框架的核心是工作窃取(work-stealing)算法。那么什么是工作窃取算法呢?假如我们有一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。

ForkJoinPool是一个运行ForkJoinTask的线程池,同ThreadPoolExecutor一样,也继承了AbstractExecutorService。ForkJoinPool的每个工作线程都维护着一个工作队列,这是一个双端队列Deque,里面存放着任务ForkJoinTask。每个工作线程在运行过程中,产生的新任务会放到工作队列的队尾。工作线程在处理自己工作队列任务时,每次是从队尾取任务。当自己的工作队列清空后,会尝试去窃取其他工作队列的任务,且是从队首窃取。

并行流处理过程中,用的ForkJoinTask是CountedCompleter的几个子类,如forEach()操作对应的是ForEachTask,forEachOrdered()操作对应的是ForEachOrderedTask,reduce()操作对应的是ReduceTask。

以Lists.newArrayList(1, 2, 3).parallelStream().forEach(System.out::println);为例,用的ForkJoinPool实例是

跟到ForEachTask的compute()方法,ForEachTask 第283行,AbstractTask.suggestTargetSize(sizeEstimate);

AbstractTask的suggestTargetSize()方法实现是:

    public static long suggestTargetSize(long sizeEstimate) {
        long est = sizeEstimate / LEAF_TARGET;
        return est > 0L ? est : 1L;
    }

其中,LEAF_TARGET值定义是

static final int LEAF_TARGET = ForkJoinPool.getCommonPoolParallelism() << 2;

这里就调用了ForkJoinPool的getCommonPoolParallelism()静态方法。ForkJoinPool有一个static块,里面调用ForkJoinPool的makeCommonPool()静态方法给静态的ForkJoinPool实例common赋值。makeCommonPool()方法内部调用了ForkJoinPool的private的构造方法,其中第一个参数并行度的值是CPU核心数-1。取CPU核心数的代码是Runtime.getRuntime().availableProcessors()。

        if (parallelism < 0 &&
            (parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0)
            parallelism = 1;
        if (parallelism > MAX_CAP)
            parallelism = MAX_CAP;

我们如果想用ForkJoinPool实现自己的业务,则需要继承ForkJoinTask。更简单点,只需继承ForkJoinTask的子类RecursiveTask或者RecursiveAction,重写compute()方法即可。

案例见:https://blog.csdn.net/niyuelin1990/article/details/78658251

原文地址:https://www.cnblogs.com/koushr/p/12044576.html