我想通过一种方法将Runnable
任务提交到forkjoinpool:
forkJoinPool.submit(Runnable task)
注意,我使用JDK 7。
在引擎盖下,它们被转换为forkjointask对象。我知道,当任务被递归地分为较小的任务时,forkjoinpool是有效的。
问题:
如果没有递归,偷偷摸摸仍然在福克乔恩浦中工作?
在这种情况下是值得的吗?
更新1:任务很小,可能是不平衡的。即使对于严格平等的任务,诸如上下文切换,线程调度,停车,页面遗漏等诸如导致 IMBALANCE 的方式。
更新2:道格·利亚(Doug Lea)在同意JSR-166兴趣小组中写道:
当所有任务都是异步时,这也大大改善了吞吐量 提交给游泳池而不是分叉,这成为合理的 结构演员框架的方法以及许多普通服务 否则,您可能会使用ThreadPoolExecutor进行。
我想,当涉及到相当小的CPU结合任务时,由于这种优化,Forkjoinpool是必经之路。要点是这些任务已经很小,不需要递归分解。工作窃取有效,无论它是一项大任务还是小任务 - 一个繁忙工人的deque尾巴可以抓住另一个免费工人的任务。
更新3:forkjoinpool的可伸缩性 - 乒乓球的Akka团队的基准测试表现出色。
尽管如此,要更有效地应用forkjoinpool,就需要调整性能。
ForkJoinPool
源代码具有一个不错的部分,称为"实现概述",请阅读以获取最终的真相。下面的解释是我对JDK 8U40的理解。
从第一天开始,ForkJoinPool
每个工作线程都有一个工作队列(我们称它们为"工人队列")。将分叉的任务推入本地工人队列,准备再次由工人弹出并被执行 - 换句话说,从工作人员线程角度来看,它看起来像是堆栈。当工人耗尽其工人队列时,它四处走动,并试图从其他工人队列中窃取任务。那就是"窃取工作" 。
现在,在(IIRC)JDK 7U12之前,ForkJoinPool
有一个全局提交队列。当Worker线程用完本地任务时,还需要窃取任务时,他们到达那里,试图查看是否有外部工作。在此设计中,对于常规的ThreadPoolExecutor
,ArrayBlockingQueue
支持的是没有优势。
之后它发生了很大变化。在此提交队列被确定为严重的性能瓶颈之后,Doug Lea等人。也将提交队列加入。事后看来,这是一个明显的想法:您可以重用大多数用于工人队列的机制。您甚至可以松散地分发每个工人线程的提交队列。现在,外部提交进入提交队列之一。然后,没有工作要做的工人可以首先查看与特定工人相关的提交队列,然后四处寻找他人的提交队列。一个人也可以称呼也可以"偷窃"。
我已经看到许多工作负载受益于此。ForkJoinPool
的这种特殊设计优势即使对于普通的非追回任务,也很久以前也得到了认可。并发利益的许多用户要求一个简单的偷窃执行者,而没有所有ForkJoinPool
Arcanery。这是我们在JDK 8中使用Executors.newWorkStealingPool()
的原因之一 - 当前委派给ForkJoinPool
,但开放以提供更简单的实现。