Java的叉/join与ExecutorService – 何时使用?
我刚读完这篇文章: Java-5 ThreadPoolExecutor比Java-7 ForkJoinPool有什么优势? 觉得答案不够直白。
你能用简单的语言和例子来解释一下,Java 7的Fork-Join框架和旧的解决scheme之间的权衡是什么?
我还读了关于Java技巧的Java #1的命中:什么时候使用来自javaworld.com的 ForkJoinPool vs ExecutorService ,但文章没有回答标题问题时 ,它主要谈论API差异…
分叉连接允许您轻松执行分割和征服作业,如果要在ExecutorService中执行,必须手动执行。 在实践中,ExecutorService通常用于同时处理许多独立的请求(又名事务),并在您想要加速一个连贯的工作时进行分叉连接。
分叉join对于recursion问题特别有用 ,其中任务涉及运行子任务,然后处理其结果。 (这通常被称为“分而治之”……但是并不能揭示其本质特征。)
如果你尝试使用传统的线程来解决像这样的recursion问题(例如,通过一个ExecutorService),那么你最终会被绑定的线程等待其他线程向其传递结果。
另一方面,如果问题没有这些特征,那么使用fork-join就没有什么实际的好处。
这里有一个“Java技巧”文章,详细介绍:
Java 8在Executors中提供了更多的API
static ExecutorService newWorkStealingPool()
使用所有可用的处理器创build工作线程池作为其目标并行级别。
通过添加此API, Executor提供了不同types的ExecutorService选项。
根据您的要求,您可以select其中之一或者您可以查看ThreadPoolExecutor ,它可以更好地控制有界任务队列大小, RejectedExecutionHandler
机制。
-
static ExecutorService newFixedThreadPool(int nThreads)
创build一个线程池,重用使用共享的无界队列的固定数量的线程。
-
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创build一个线程池,可以安排命令在给定延迟后运行,或定期执行。
-
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
创build一个可根据需要创build新线程的线程池,但在可用时会重用之前构build的线程,并在需要时使用提供的ThreadFactory创build新线程。
-
static ExecutorService newWorkStealingPool(int parallelism)
创build一个线程池,维护足够的线程来支持给定的并行性级别,并可以使用多个队列来减less争用。
这些API中的每一个都旨在满足您的应用程序的相应业务需求。 使用哪一个将取决于您的用例要求。
例如
-
如果要按照到达顺序处理所有提交的任务,只需使用
newFixedThreadPool(1)
-
如果你想优化大recursion任务的性能,使用
ForkJoinPool
或newWorkStealingPool
-
如果您想要定期执行某些任务或将来某个时间执行某些任务,请使用
newScheduledThreadPool
看一看PeterLawrey
关于ExecutorService
用例的更好的文章 。
相关的SE问题:
java fork / join pool,ExecutorService和CountDownLatch
Fork-Join框架是Executor框架的扩展,特别解决了recursionmultithreading程序中的“等待”问题。 实际上,新的Fork-Join框架类都是从Executor框架的现有类中扩展而来的。
Fork-Join框架的核心有两个特点
- 工作窃取(一个空闲的线程从一个线程中窃取工作,这个线程的任务比当前处理的要多)
- recursion地分解任务并收集结果的能力。 (显然,这个要求必须随着并行处理概念的出现而出现……但在Java 7之前,在Java中缺乏一个坚实的实现框架)
如果并行处理需求是严格recursion的,那么除了去Fork-Join之外别无select,否则执行者或Fork-Join框架都应该这样做,尽pipeFork-Join可以说因为空闲线程而更好地利用资源从繁忙的线程中“窃取”一些任务。
Fork Join是ExecuterService的一个实现。 主要区别在于这个实现创build了DEQUE工作者池。 从哪里插入任务,但从任何一方撤回。 这意味着如果您创build了new ForkJoinPool()
,它将查找可用的CPU并创build多个工作线程。 然后在每个线程上均匀分配负载。 但是如果一个线程工作的很慢而另一个线程的速度很快,他们将从慢线程中select任务。 从背面。 以下步骤将更好地说明盗窃行为。
阶段1(最初):
W1→5,4,3,2,1
W2 – > 10,9,8,7,6
阶段2:
W1 – > 5,4
W2→10,9,8,7,
阶段3:
W1 – > 10,5,4
W2→9,8,7,
Executor服务创build被询问的线程数量,并应用阻塞队列来存储所有剩余的等待任务。 如果您使用了cachedExecuterService,它将为每个作业创build单个线程,并且不会有等待队列。