Java 的 Fork/Join 与 ExecutorService - 何时使用哪个?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21156599/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java's Fork/Join vs ExecutorService - when to use which?
提问by Parobay
I just finished reading this post: What's the advantage of a Java-5 ThreadPoolExecutor over a Java-7 ForkJoinPool?and felt that the answer is not straight enough.
我刚刚读完这篇文章:Java-5 ThreadPoolExecutor 与 Java-7 ForkJoinPool 相比有什么优势?并觉得答案不够直接。
Can you explain in simple language and examples, what are the trade-offsbetween Java 7's Fork-Join framework and the older solutions?
您能否用简单的语言和示例来解释Java 7 的 Fork-Join 框架与旧解决方案之间的权衡?
I also read the Google's #1 hit on the topic Java Tip: When to use ForkJoinPool vs ExecutorServicefrom javaworld.combut the article doesn't answer the title question when, it talks about api differences mostly ...
我还阅读了来自javaworld.com的谷歌关于Java Tip: When to use ForkJoinPool vs ExecutorService主题的#1热门文章,但文章没有回答标题问题when,它主要讨论了api差异......
采纳答案by Jakub Kubrynski
Fork-join allows you to easily execute divide and conquer jobs, which have to be implemented manually if you want to execute it in ExecutorService
. In practice ExecutorService
is usually used to process many independent requests (aka transaction) concurrently, and fork-join when you want to accelerate one coherent job.
Fork-join 允许您轻松执行分治作业,如果您想在ExecutorService
. 在实践ExecutorService
中通常用于并发处理许多独立的请求(又名事务),当你想加速一个连贯的工作时使用 fork-join。
回答by Stephen C
Fork-join is particularly good for recursiveproblems, where a task involves running subtasks and then processing their results. (This is typically called "divide and conquer" ... but that doesn't reveal the essential characteristics.)
Fork-join 特别适用于递归问题,其中任务涉及运行子任务,然后处理它们的结果。(这通常被称为“分而治之”......但这并没有揭示基本特征。)
If you try to solve a recursive problem like this using conventional threading (e.g. via an ExecutorService) you end up with threads tied up waiting for other threads to deliver results to them.
如果您尝试使用传统线程(例如通过 ExecutorService)来解决这样的递归问题,您最终会被线程捆绑等待其他线程将结果传递给它们。
On the other hand, if the problem doesn't have those characteristics, there is no real benefit from using fork-join.
另一方面,如果问题不具有这些特征,则使用 fork-join 没有真正的好处。
References:
参考:
回答by Murali Mohan
Fork-Join framework is an extension to Executor framework to particularly address 'waiting' issues in recursive multi-threaded programs. In fact, the new Fork-Join framework classes all extend from the existing classes of the Executor framework.
Fork-Join 框架是 Executor 框架的扩展,专门用于解决递归多线程程序中的“等待”问题。事实上,新的 Fork-Join 框架类都是从 Executor 框架的现有类扩展而来的。
There are 2 characteristics central to Fork-Join framework
Fork-Join 框架有两个核心特征
- Work Stealing (An idle thread steals work from a thread having tasks queued up more than it can process currently)
- Ability to recursively decompose the tasks and collect the results. (Apparently, this requirement must have popped up along with the conception of the notion of parallel processing... but lacked a solid implementation framework in Java till Java 7)
- 工作窃取(空闲线程从队列中的任务超过其当前处理能力的线程中窃取工作)
- 能够递归分解任务并收集结果。(显然,这个要求一定是随着并行处理概念的出现而出现的……但是直到 Java 7 之前,Java 中缺乏一个可靠的实现框架)
If the parallel processing needs are strictly recursive, there is no choice but to go for Fork-Join, otherwise either of executor or Fork-Join framework should do, though Fork-Join can be said to better utilize the resources because of the idle threads 'stealing' some tasks from busier threads.
如果并行处理的需求是严格递归的,则只能选择Fork-Join,否则无论是executor还是Fork-Join框架都可以,虽然Fork-Join可以说因为空闲线程更好地利用了资源从更繁忙的线程“窃取”一些任务。
回答by Ravindra babu
Java 8 provides one more API in Executors
Java 8 在 Executors 中又提供了一个 API
static ExecutorService newWorkStealingPool()
Creates a work-stealing thread pool using all available processors as its target parallelism level.
使用所有可用的处理器作为其目标并行度级别来创建窃取工作的线程池。
With addition of this API,Executorsprovides different types of ExecutorServiceoptions.
通过添加这个 API,Executors提供了不同类型的ExecutorService选项。
Depending on your requirement, you can choose one of them or you can look out for ThreadPoolExecutorwhich provides better control on Bounded Task Queue Size, RejectedExecutionHandler
mechanisms.
根据您的要求,您可以选择其中之一,或者您可以寻找ThreadPoolExecutor,它可以更好地控制有界任务队列大小、RejectedExecutionHandler
机制。
static ExecutorService newFixedThreadPool(int nThreads)
Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically.
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available, and uses the provided ThreadFactory to create new threads when needed.
static ExecutorService newWorkStealingPool(int parallelism)
Creates a thread pool that maintains enough threads to support the given parallelism level, and may use multiple queues to reduce contention.
static ExecutorService newFixedThreadPool(int nThreads)
创建一个线程池,该池重用固定数量的线程在共享的无界队列中运行。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个线程池,可以安排命令在给定延迟后运行,或定期执行。
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
创建一个线程池,根据需要创建新线程,但在可用时将重用先前构造的线程,并在需要时使用提供的 ThreadFactory 创建新线程。
static ExecutorService newWorkStealingPool(int parallelism)
创建一个线程池,维护足够的线程以支持给定的并行度级别,并且可以使用多个队列来减少争用。
Each of these APIs are targeted to fulfil respective business needs of your application. Which one to use will depend on your use case requirement.
这些 API 中的每一个都旨在满足您的应用程序各自的业务需求。使用哪一种取决于您的用例要求。
e.g.
例如
If you want to process all submitted tasks in order of arrival, just use
newFixedThreadPool(1)
If you want to optimize performance of big computation of recursive tasks, use
ForkJoinPool
ornewWorkStealingPool
If you want to execute some tasks periodically or at certain time in future, use
newScheduledThreadPool
如果要按到达顺序处理所有提交的任务,只需使用
newFixedThreadPool(1)
如果要优化递归任务的大计算性能,请使用
ForkJoinPool
或newWorkStealingPool
如果您想定期或在将来的某个时间执行某些任务,请使用
newScheduledThreadPool
Have a look at one more nice articleby PeterLawrey
on ExecutorService
use cases.
看看一个更漂亮的文章通过PeterLawrey
对ExecutorService
用例。
Related SE question:
相关 SE 问题:
回答by krmanish007
Fork Join is an implementation of ExecuterService. The main difference is that this implementation creates DEQUE worker pool. Where task is inserted from oneside but withdrawn from any side. It means if you have created new ForkJoinPool()
it will look for the available CPU and create that many worker thread. It then distribute the load evenly across each thread. But if one thread is working slowly and others are fast, they will pick the task from the slow thread. from the backside. The below steps will illustrate the stealing better.
Fork Join 是 ExecuterService 的一个实现。主要区别在于此实现创建了 DEQUE 工作池。任务从一侧插入但从任何一侧撤回。这意味着如果您创建了new ForkJoinPool()
它,它将寻找可用的 CPU 并创建那么多的工作线程。然后它在每个线程上均匀地分配负载。但是如果一个线程运行缓慢而其他线程运行速度很快,它们就会从慢速线程中挑选任务。从背面。以下步骤将更好地说明窃取。
Stage 1 (initially):
W1 -> 5,4,3,2,1
W2 -> 10,9,8,7,6
第一阶段(初始):
W1 -> 5,4,3,2,1
W2 -> 10,9,8,7,6
Stage 2:
W1 -> 5,4
W2 -> 10,9,8,7,
第 2 阶段:
W1 -> 5,4
W2 -> 10,9,8,7,
Stage 3:
W1 -> 10,5,4
W2 -> 9,8,7,
第 3 阶段:
W1 -> 10,5,4
W2 -> 9,8,7 ,
Whereas Executor service creates asked number of thread, and apply a blocking queue to store all the remaining waiting task. If you have used cachedExecuterService, it will create single thread for each job and there will be no waiting queue.
而 Executor 服务会创建请求数量的线程,并应用一个阻塞队列来存储所有剩余的等待任务。如果您使用了 cachedExecuterService,它将为每个作业创建单个线程,并且不会有等待队列。
回答by bhh1988
Brian Goetz describes the situation best: https://www.ibm.com/developerworks/library/j-jtp11137/index.html
Brian Goetz 最好地描述了这种情况:https: //www.ibm.com/developerworks/library/j-jtp11137/index.html
Using conventional thread pools to implement fork-join is also challenging because fork-join tasks spend much of their lives waiting for other tasks. This behavior is a recipe for thread starvation deadlock, unless the parameters are carefully chosen to bound the number of tasks created or the pool itself is unbounded. Conventional thread pools are designed for tasks that are independent of each other and are also designed with potentially blocking, coarse-grained tasks in mind — fork-join solutions produce neither.
使用传统的线程池来实现 fork-join 也具有挑战性,因为 fork-join 任务大部分时间都在等待其他任务。这种行为是线程饥饿死锁的一个秘诀,除非仔细选择参数来限制创建的任务数量或池本身是无界的。传统的线程池是为相互独立的任务而设计的,并且在设计时也考虑到了潜在的阻塞、粗粒度的任务——fork-join 解决方案两者都不会产生。
I recommend reading the whole post, as it has a good example of why you'd want to use a fork-join pool. It was written before ForkJoinPool became official, so the coInvoke()
method he refers to became invokeAll()
.
我建议阅读整篇文章,因为它有一个很好的例子说明为什么要使用 fork-join 池。是在 ForkJoinPool 正式之前写的,所以coInvoke()
他所指的方法变成了invokeAll()
.