Java Fork / Join vs ExecutorService - 何时使用哪个?

时间:2014-01-16 08:21:33

标签: java multithreading concurrency executorservice fork-join

我刚读完这篇文章:What's the advantage of a Java-5 ThreadPoolExecutor over a Java-7 ForkJoinPool?并觉得答案不够直接。

您能用简单的语言和示例解释一下Java 7的Fork-Join框架与旧解决方案之间的权衡取舍是什么?

我还阅读了来自Java Tip: When to use ForkJoinPool vs ExecutorService的关于主题javaworld.com的Google排名第一,但文章没有回答标题问题何时,它主要讨论api的差异...

6 个答案:

答案 0 :(得分:43)

Fork-join允许您轻松执行分而治之的作业,如果您想在ExecutorService中执行,则必须手动执行。在实践中,ExecutorService通常用于同时处理许多独立请求(也称为事务),并且当您想要加速一个连贯的作业时使用fork-join。

答案 1 :(得分:30)

fork-join特别适用于递归问题,其中任务涉及运行子任务然后处理其结果。 (这通常被称为“分而治之”......但这并未揭示其本质特征。)

如果你尝试使用传统的线程解决这样的递归问题(例如通过ExecutorService),你最终会遇到等待其他线程向其传递结果的线程。

另一方面,如果问题没有这些特征,使用fork-join没有任何实际好处。


Here's a "Java Tips" article that goes into more detail:

答案 2 :(得分:13)

Java 8在Executors中提供了一个API

static ExecutorService  newWorkStealingPool()
  

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

添加此API后,Executors会提供不同类型的ExecutorService选项。

根据您的要求,您可以选择其中一个,或者您可以留意ThreadPoolExecutor,它可以更好地控制有界任务队列大小RejectedExecutionHandler机制。

  1. <强> static ExecutorService newFixedThreadPool(int nThreads)

      

    创建一个线程池,该线程池重用在共享的无界队列中运行的固定数量的线程。

  2. <强> static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

      

    创建一个线程池,可以安排命令在给定的延迟后运行,或者定期执行。

  3. <强> static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

      

    创建一个线程池,根据需要创建新线程,但会在可用时重用以前构造的线程,并在需要时使用提供的ThreadFactory创建新线程。

  4. <强> static ExecutorService newWorkStealingPool(int parallelism)

      

    创建一个线程池,该线程池维护足够的线程以支持给定的并行级别,并且可以使用多个队列来减少争用。

  5. 这些API中的每一个都旨在满足您的应用程序的各自业务需求。使用哪一个将取决于您的用例要求。

    e.g。

    1. 如果您想按照到达顺序处理所有提交的任务,请使用newFixedThreadPool(1)

    2. 如果要优化递归任务的大型计算性能,请使用ForkJoinPoolnewWorkStealingPool

    3. 如果您想定期或在将来的某个时间执行某些任务,请使用newScheduledThreadPool

    4. PeterLawrey个用例的ExecutorService上查看一个更好的article

      相关的SE问题:

      java Fork/Join pool, ExecutorService and CountDownLatch

答案 3 :(得分:5)

Brian Goetz最好地描述了这种情况:https://www.ibm.com/developerworks/library/j-jtp11137/index.html

  

使用传统的线程池来实现fork-join也很有挑战性,因为fork-join任务会花费大量时间等待其他任务。此行为是线程饥饿死锁的一个方法,除非仔细选择参数来绑定创建的任务数或池本身是无限制的。传统的线程池是为彼此独立的任务而设计的,并且在设计时也考虑了潜在的阻塞,粗粒度的任务 - fork-join解决方案都不生成。

我建议阅读整篇文章,因为它有一个很好的例子说明你为什么要使用fork-join池。它是在ForkJoinPool成为官方之前编写的,因此他引用的coInvoke()方法成为invokeAll()

答案 4 :(得分:4)

Fork-Join框架是Executor框架的扩展,用于特别解决“等待”问题。递归多线程程序中的问题。实际上,新的Fork-Join框架类都是从Executor框架的现有类扩展而来的。

Fork-Join框架有两个中心特征

  • 工作窃取(空闲线程从具有任务的线程中窃取工作 排队的时间超过目前可以处理的数量)
  • 能够递归分解任务并收集结果。 (显然,这个要求必须随着这个要求一起出现 并行处理概念的概念......但缺乏可靠性 Java中的实现框架直到Java 7)

如果并行处理需求是严格递归的,那么除了使用Fork-Join之外别无选择,否则执行者或Fork-Join框架都应该这样做,尽管可以说Fork-Join可以更好地利用资源,因为闲置线程偷窃&#39;繁忙的线程中的一些任务。

答案 5 :(得分:4)

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.

Stage 1 (initially):
W1 -> 5,4,3,2,1
W2 -> 10,9,8,7,6

Stage 2:
W1 -> 5,4
W2 -> 10,9,8,7,

Stage 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.