35

Java 5 以 Executor 框架的形式引入了对线程池异步任务执行的支持,其核心是 java.util.concurrent.ThreadPoolExecutor 实现的线程池。Java 7 以 java.util.concurrent.ForkJoinPool 的形式添加了一个替代线程池。

查看它们各自的 API,ForkJoinPool 在标准场景中提供了 ThreadPoolExecutor 功能的超集(尽管严格来说 ThreadPoolExecutor 提供了比 ForkJoinPool 更多的调优机会)。除此之外,观察到 fork/join 任务似乎更快(可能是由于工作窃取调度程序),需要的线程肯定更少(由于非阻塞连接操作),人们可能会觉得 ThreadPoolExecutor 已被取代ForkJoinPool。

但这真的正确吗?我读过的所有材料似乎都总结了两种线程池之间相当模糊的区别:

  • ForkJoinPool 用于许多相关的、任务生成的、短的、几乎不会阻塞(即计算密集型)任务
  • ThreadPoolExecutor 用于少数、独立、外部生成、长、有时阻塞的任务

这种区别完全正确吗?我们能说得更具体一点吗?

4

4 回答 4

18

ThreadPool (TP) 和 ForkJoinPool (FJ) 针对不同的用例。主要区别在于不同执行者使用的队列数量决定了哪种类型的问题更适合任一执行者。

FJ 执行器有 n 个(又名并行级别)单独的并发队列(deques),而 TP 执行器只有一个并发队列(这些队列/deques 可能是不遵循 JDK Collections API 的自定义实现)。因此,在您生成大量(通常是相对较短的运行时间)任务的场景中,FJ 执行器将执行得更好,因为独立队列将最大限度地减少并发操作,并且不频繁的窃取将有助于负载平衡。在TP中,由于单队列,每次工作出队时都会有并发操作,这将成为一个相对的瓶颈并限制性能。

相反,如果长时间运行的任务相对较少,则 TP 中的单个队列不再是性能瓶颈。然而,n 独立队列和相对频繁的工作窃取尝试现在将成为 FJ 的瓶颈,因为可能会有许多徒劳的尝试窃取工作,这会增加开销。

此外,FJ 中的工作窃取算法假设从双端队列窃取的(较旧的)任务将产生足够的并行任务以减少窃取次数。例如,在旧任务等同于更大数组的快速排序或合并排序中,这些任务将生成更多任务并保持队列非空并减少总体窃取次数。如果在给定的应用程序中不是这种情况,那么频繁的窃取尝试再次成为瓶颈。ForkJoinPool的 javadoc 中也指出了这一点:

此类提供状态检查方法(例如 getStealCount()),旨在帮助开发、调整和监控 fork/join 应用程序。

于 2012-04-28T18:39:56.113 回答
11

推荐阅读http://gee.cs.oswego.edu/dl/jsr166/dist/docs/ 来自 ForkJoinPool 的文档:

ForkJoinPool 与其他类型的 ExecutorService 的不同之处主要在于采用了工作窃取:池中的所有线程都尝试查找并执行提交到池和/或由其他活动任务创建的任务(如果不存在,则最终阻塞等待工作) . 当大多数任务产生其他子任务(大多数 ForkJoinTasks 也是如此)时,以及当许多小任务从外部客户端提交到池时,这可以实现高效处理。尤其是在构造函数中将 asyncMode 设置为 true 时,ForkJoinPools 也可能适用于从未加入的事件式任务。

fork join 框架对于并行执行很有用,而 executor 服务允许并发执行,并且有区别。看到这个这个

fork join 框架还允许工作窃取(使用 Deque)。

这篇文章很好读。

于 2012-02-14T12:29:40.550 回答
2

AFAIK,ForkJoinPool如果您的工作量很大并且您希望它自动分解,则效果最好。 ThreadPoolExecutor如果您知道您希望如何分解工作,这是一个更好的选择。出于这个原因,我倾向于使用后者,因为我已经确定了我希望如何分解工作。因此,它并不适合每个人。

当涉及到相对随机的业务逻辑片段时,ThreadPoolExecutor 将完成您需要的一切,这毫无价值,所以为什么让它比您需要的更复杂。

于 2012-02-14T12:30:21.733 回答
1

让我们比较一下构造函数的区别:

线程池执行器

ThreadPoolExecutor(int corePoolSize, 
                   int maximumPoolSize, 
                   long keepAliveTime, 
                   TimeUnit unit, 
                   BlockingQueue<Runnable> workQueue, 
                   ThreadFactory threadFactory,
                   RejectedExecutionHandler handler)

ForkJoinPool

ForkJoinPool(int parallelism,
            ForkJoinPool.ForkJoinWorkerThreadFactory factory,
            Thread.UncaughtExceptionHandler handler,
            boolean asyncMode)

我看到的唯一优势ForkJoinPool:空闲线程的工作窃取机制。

Java 8 在Executors 中引入了另外一种 API - newWorkStealingPool来创建工作窃取池。您不必创建RecursiveTaskRecursiveAction但仍然可以使用ForkJoinPool.

public static ExecutorService newWorkStealingPool()

创建一个工作窃取线程池,使用所有可用处理器作为其目标并行度级别。

ThreadPoolExecutor 相对于 ForkJoinPool 的优势:

  1. 您可以控制任务队列的大小,ThreadPoolExecutor不像ForkJoinPool.
  2. 当您的容量用完时,您可以执行拒绝政策,这与ForkJoinPool

我喜欢这两个ThreadPoolExecutor使系统健康保持良好状态的功能。

编辑:

查看这篇文章,了解各种类型的 Executor Service 线程池的用例和ForkJoin Pool功能的评估。

于 2016-01-18T13:19:39.420 回答