为啥parallelStream 使用ForkJoinPool,而不是普通的线程池?

Posted

技术标签:

【中文标题】为啥parallelStream 使用ForkJoinPool,而不是普通的线程池?【英文标题】:Why does parallelStream use a ForkJoinPool, not a normal thread pool?为什么parallelStream 使用ForkJoinPool,而不是普通的线程池? 【发布时间】:2020-08-29 03:22:35 【问题描述】:

参考Java's Fork/Join vs ExecutorService - when to use which?,通常使用传统的线程池来处理很多独立的请求; ForkJoinPool 用于处理连贯/递归任务,其中一个任务可能会产生另一个子任务并稍后加入。

那么,为什么 Java-8 的 parallelStream 默认使用 ForkJoinPool 而不是传统的执行器?

在很多情况下,我们在stream()parallelStream() 之后使用forEach(),然后提交一个函数式接口作为参数。在我看来,这些任务是独立的,不是吗?

【问题讨论】:

forEach 是十几个终端操作中的一个。即使您“在许多情况下”使用它,它也肯定不会驱动仍然必须支持所有这些的软件的设计决策。除此之外,我认为forEach 是最没用的终端操作,它经常导致开发人员编写损坏的“用另一个名字循环”代码。 【参考方案1】:

一个重要的事情是ForkJoinPool 也可以执行“正常”任务(例如RunnableCallable),因此它不仅仅用于递归创建的任务。

另一个(重要的)事情是ForkJoinPool 有多个队列,一个用于每个工作线程的任务,一个普通的执行器(例如ThreadPoolExecutor)只有一个。这对他们应该运行什么样的任务有很大影响。

普通执行器必须执行的任务越小越多,将任务分配给工作人员的同步开销就越高。如果大部分任务都很小,worker会经常访问内部任务队列,导致同步开销。

这就是ForkJoinPool 以其多个队列而大放异彩的地方。每个worker只是从自己的队列中获取任务,大部分时间不需要通过阻塞来同步,如果它是空的,它可以从另一个worker那里窃取任务,但是从队列的另一端,这也很少会导致同步开销,因为工作窃取应该是相当罕见的。

现在这与并行流有什么关系?流框架设计为易于使用。当您想轻松地将某些内容拆分为许多并发任务时,应该使用并行流,其中所有任务都相当小且简单。这就是ForkJoinPool 是合理选择的地方。它在大量较小的任务上提供了更好的性能,并且它可以处理更长的任务,如果必须的话。

【讨论】:

以上是关于为啥parallelStream 使用ForkJoinPool,而不是普通的线程池?的主要内容,如果未能解决你的问题,请参考以下文章

使用 parallelStream 时抛出 InterruptedException - Java [重复]

带有 spring 注释方法的 Java .parallelStream()

Java 8 的 parallelStream 中产生了多少线程?

java 8 parallelStream() 和 sorted()

组合器在 java parallelStream reduce 中的实际作用是啥

Java 8里 Stream和parallelStream的区别