Java-5 ThreadPoolExecutor 相对于 Java-7 ForkJoinPool 有什么优势?
Java 5以Executor框架的形式引入了对线程池异步任务执行的支持,其核心是java.util.concurrent.ThreadPoolExecutor实现的线程池。 Java 7 添加了 java.util.concurrent.ForkJoinPool 形式的替代线程池。
从它们各自的 API 来看,ForkJoinPool 在标准场景中提供了 ThreadPoolExecutor 功能的超集(尽管严格来说 ThreadPoolExecutor 比 ForkJoinPool 提供了更多的调优机会)。除此之外,还观察到 fork/join 任务似乎更快(可能是由于工作窃取调度程序),需要的线程肯定更少(由于非阻塞连接操作),人们可能会觉得 ThreadPoolExecutor 已被 ForkJoinPool 取代。
但这真的正确吗?我读过的所有材料似乎总结了两种类型的线程池之间相当模糊的区别:
- ForkJoinPool 适用于许多、依赖的、任务生成的、短的、几乎不会阻塞(即计算密集型)任务
- ThreadPoolExecutor 适用于少数 任务、独立的、外部生成的、长的、有时阻塞的任务
这种区别是否正确?我们能对此说得更具体一些吗?
Java 5 has introduced support for asynchronous task execution by a thread pool in the form of the Executor framework, whose heart is the thread pool implemented by java.util.concurrent.ThreadPoolExecutor. Java 7 has added an alternative thread pool in the form of java.util.concurrent.ForkJoinPool.
Looking at their respective API, ForkJoinPool provides a superset of ThreadPoolExecutor's functionality in standard scenarios (though strictly speaking ThreadPoolExecutor offers more opportunities for tuning than ForkJoinPool). Adding to this the observation that
fork/join tasks seem to be faster (possibly due to the work stealing scheduler), need definitely fewer threads (due to the non-blocking join operation), one might get the impression that ThreadPoolExecutor has been superseded by ForkJoinPool.
But is this really correct? All the material I have read seems to sum up to a rather vague distinction between the two types of thread pools:
- ForkJoinPool is for many, dependent, task-generated, short, hardly ever blocking (i.e. compute-intensive) tasks
- ThreadPoolExecutor is for few, independent, externally-generated, long, sometimes blocking tasks
Is this distinction correct at all? Can we say anything more specific about this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
ThreadPool (TP) 和 ForkJoinPool (FJ) 针对不同的用例。主要区别在于不同执行器所使用的队列数量,这决定了哪种类型的问题更适合任一执行器。
FJ 执行器有 n 个(又名并行级别)独立的并发队列(双端队列),而 TP 执行器只有一个并发队列(这些队列/双端队列可能是不遵循 JDK Collections API 的自定义实现)。因此,在生成大量(通常运行时间相对较短)任务的情况下,FJ 执行器将表现得更好,因为独立队列将最大限度地减少并发操作,并且不频繁的窃取将有助于负载平衡。在TP中,由于单队列,每次工作出队时都会有并发操作,这将成为相对瓶颈并限制性能。
相反,如果长时间运行的任务相对较少,TP 中的单个队列就不再是性能瓶颈。然而,n 个独立的队列和相对频繁的工作窃取尝试现在将成为 FJ 的瓶颈,因为可能有许多徒劳的尝试窃取工作,这会增加开销。
此外,FJ 中的工作窃取算法假设从双端队列中窃取的(较旧的)任务将产生足够的并行任务以减少窃取次数。例如,在快速排序或合并排序中,较旧的任务相当于较大的数组,这些任务将生成更多任务并保持队列非空并减少总体窃取的数量。如果给定应用程序的情况并非如此,那么频繁的窃取尝试将再次成为瓶颈。 ForkJoinPool 的 javadoc 中也指出了这一点:
ThreadPool (TP) and ForkJoinPool (FJ) are targeted towards different use cases. The main difference is in the number of queues employed by the different executors which decide what type of problems are better suited to either executor.
The FJ executor has n (aka parallelism level) separate concurrent queues (deques) while the TP executor has only one concurrent queue (these queues/deques maybe custom implementations not following the JDK Collections API). As a result, in scenarios where you have a large number of (usually relatively short running) tasks generated, the FJ executor will perform better as the independent queues will minimize concurrent operations and infrequent steals will help with load balancing. In TP due to the single queue, there will be concurrent operations every time work is dequeued and it will act as a relative bottleneck and limit performance.
In contrast, if there are relatively fewer long-running tasks the single queue in TP is no longer a bottleneck for performance. However, the n-independent queues and relatively frequent work-stealing attempts will now become a bottleneck in FJ as there can be possibly many futile attempts to steal work which add to overhead.
In addition, the work-stealing algorithm in FJ assumes that (older) tasks stolen from the deque will produce enough parallel tasks to reduce the number of steals. E.g. in quicksort or mergesort where older tasks equate to larger arrays, these tasks will generate more tasks and keep the queue non-empty and reduce the number of overall steals. If this is not the case in a given application then the frequent steal attempts again become a bottleneck. This is also noted in the javadoc for ForkJoinPool:
推荐阅读 http://gee.cs.oswego.edu/dl/jsr166 /dist/docs/
来自 ForkJoinPool 的文档:
fork join 框架对于并行执行很有用,而执行器服务允许并发执行,这是有区别的。请参阅此和此。
fork join 框架还允许窃取工作(使用 Deque)。
这篇文章值得一读。
Recommended Reading http://gee.cs.oswego.edu/dl/jsr166/dist/docs/
From the docs for ForkJoinPool:
The fork join framework is useful for parallel execution while executor service allows for concurrent execution and there is a difference. See this and this.
The fork join framework also allows for work stealing (usage of a Deque).
This article is a good read.
AFAIK,如果您的工作量很大并且希望它自动分解,
ForkJoinPool
效果最好。如果您知道如何分解工作,ThreadPoolExecutor
是更好的选择。出于这个原因,我倾向于使用后者,因为我已经决定了如何分解工作。因此,它并不适合所有人。当涉及到相对随机的业务逻辑时,ThreadPoolExecutor 会做您需要的一切,这毫无价值,所以为什么要让它比您需要的更复杂。
AFAIK,
ForkJoinPool
works best if you a large piece of work and you want it broken up automatically.ThreadPoolExecutor
is a better choice if you know how you want the work broken up. For this reason I tend to use the latter because I have determined how I want the work broken up. As such its not for every one.Its worth nothing that when it comes to relatively random pieces of business logic a ThreadPoolExecutor will do everything you need, so why make it more complicated than you need.
让我们比较一下构造函数的差异:
ThreadPoolExecutor
ForkJoinPool
我在
ForkJoinPool:空闲线程的工作窃取机制。
Java 8 在 Executors - newWorkStealingPool 创建工作窃取池。您不必创建
RecursiveTask
和RecursiveAction
,但仍然可以使用ForkJoinPool
。ThreadPoolExecutor 相对于 ForkJoinPool 的优点:
ForkJoinPool
不同,您可以在ThreadPoolExecutor
中控制任务队列大小。我喜欢 ThreadPoolExecutor 中的这两个功能,它使系统的健康状况保持良好状态。
编辑:
看看这个文章了解各种类型的执行程序服务线程池的用例以及ForkJoin 池 功能。
Let's compare the differences in constructors:
ThreadPoolExecutor
ForkJoinPool
The only advantage I have seen in
ForkJoinPool
: Work stealing mechanism by idle threads.Java 8 has introduced one more API in Executors - newWorkStealingPool to create work stealing pool. You don't have to create
RecursiveTask
andRecursiveAction
but still can useForkJoinPool
.Advantages of ThreadPoolExecutor over ForkJoinPool:
ThreadPoolExecutor
unlike inForkJoinPool
.ForkJoinPool
I like these two features in
ThreadPoolExecutor
which keeps health of system in good state.EDIT:
Have a look at this article for use cases of various types of Executor Service thread pools and evaluation of ForkJoin Pool features.