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

sqyvllje  于 2023-04-04  发布在  Java
关注(0)|答案(6)|浏览(164)

我刚阅读这篇文章:What's the advantage of a Java-5 ThreadPoolExecutor over a Java-7 ForkJoinPool?,觉得答案不够直接。
你能用简单的语言和例子解释一下,Java 7的Fork-Join框架和旧的解决方案之间的权衡是什么?
我也读了谷歌的#1击中主题Java Tip: When to use ForkJoinPool vs ExecutorServicejavaworld.com,但文章没有回答标题问题,它谈论的API差异主要是...

nbysray5

nbysray51#

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

gv8xihay

gv8xihay2#

Fork-join特别适合于 * 递归 * 问题,其中一个任务涉及运行子任务,然后处理它们的结果。(这通常被称为“分而治之”......但这并没有揭示本质特征。)
如果你试图使用传统的线程(例如通过ExecutorService)来解决这样的递归问题,你最终会发现线程被捆绑起来,等待其他线程向它们传递结果。
另一方面,如果问题不具备这些特征,那么使用fork-join就没有真实的的好处。
参考文献:

u3r8eeie

u3r8eeie3#

Java 8在Executors中提供了另一个API

static ExecutorService  newWorkStealingPool()

使用所有可用处理器作为其目标并行度级别创建工作窃取线程池。
通过添加此API,Executors提供了不同类型的ExecutorService选项。
根据您的需求,您可以选择其中之一,或者您可以寻找ThreadPoolExecutor,它可以更好地控制有限任务队列大小,RejectedExecutionHandler机制。
1.一米一米一
创建一个线程池,该线程池重用在共享无界队列上运行的固定数量的线程。
1.一米二米一x
创建一个线程池,该线程池可以计划命令在给定延迟后运行,或定期执行。
1.一米三米一x
创建一个线程池,该线程池根据需要创建新线程,但在以前构造的线程可用时将重用这些线程,并在需要时使用提供的ThreadFactory创建新线程。
1.***static ExecutorService newWorkStealingPool(int parallelism)***
创建一个线程池,该线程池维护足够的线程以支持给定的并行级别,并且可以使用多个队列来减少争用。
这些API中的每一个都旨在满足应用程序的相应业务需求。使用哪一个取决于您的用例需求。
例如
1.如果您希望按到达顺序处理所有提交的任务,只需使用newFixedThreadPool(1)
1.如果要优化递归任务的大计算性能,请使用ForkJoinPoolnewWorkStealingPool
1.如果希望定期或在将来某个时间执行某些任务,请使用newScheduledThreadPool
相关SE问题:
java Fork/Join pool, ExecutorService and CountDownLatch

ru9i0ody

ru9i0ody4#

Brian Goetz对这种情况的描述最好:https://www.ibm.com/developerworks/library/j-jtp11137/index.html
使用传统的线程池来实现fork-join也具有挑战性,因为fork-join任务花费了它们生命周期的大部分时间来等待其他任务。这种行为是线程饥饿死锁的一个处方,除非仔细选择参数来限制所创建的任务的数量,或者线程池本身是无限的。传统的线程池是为彼此独立的任务设计的,并且也被设计为具有潜在的阻塞,粗粒度的任务- fork-join解决方案两者都不产生。
我推荐阅读整篇文章,因为它有一个很好的例子来说明为什么要使用fork-join池。它是在ForkJoinPool成为官方之前写的,所以他提到的coInvoke()方法变成了invokeAll()

soat7uwm

soat7uwm5#

Fork-Join框架是Executor框架的一个扩展,专门解决递归多线程程序中的“等待”问题。事实上,新的Fork-Join框架类都是从Executor框架的现有类扩展而来的。
Fork-Join框架有两个核心特征

  • 工作窃取(一个空闲线程从一个线程窃取工作,该线程的队列中的任务超过了它当前可以处理的任务)
  • 能够递归地分解任务并收集结果。(显然,这个需求一定是沿着并行处理的概念而出现的......但直到Java 7,Java中才有一个坚实的实现框架)

如果并行处理需求是严格递归的,则别无选择,只能使用Fork-Join,否则执行器或Fork-Join框架都应该这样做,尽管Fork-Join可以说更好地利用了资源,因为空闲线程从繁忙线程“窃取”了一些任务。

sczxawaw

sczxawaw6#

Fork Join是ExecuterService的一个实现。主要的区别是这个实现创建了DEQUE工作线程池。其中任务从一侧插入,但从任何一侧撤回。这意味着如果你已经创建了new ForkJoinPool(),它将寻找可用的CPU并创建那么多的工作线程。然后它将负载均匀地分布在每个线程上。但是如果一个线程工作得很慢,而其他线程工作得很快,他们会从慢线程中选择任务。从后面。下面的步骤将更好地说明窃取。
阶段1(初始):
W1 -〉5,4,3,2,1
W2 -〉10,9,8,7,6
第二阶段:
W1 -〉5,4
W2 -〉10,9,8,7,
第三阶段:
W1 -〉10,5,4
W2 -〉9、8、7,
而Executor服务创建请求的线程数,并应用一个阻塞队列来存储所有剩余的等待任务。如果您使用了cachedExecuterService,它将为每个作业创建单个线程,并且不会有等待队列。

相关问题