ExecutorService和ForkJoinPool的内部调度机制使我有些困惑。
我了解ExecutorService的调度已完成this way。
一堆任务排队。线程可用后,它将处理第一个可用任务,依此类推。
同时,一个ForkJoinPool呈现为与众不同,因为它使用了工作窃取算法。如果我理解正确,则意味着一个线程可以从另一个线程中窃取某些任务。
但是,我不太了解与ExecutorService中实现的机制的区别。据我了解,两种机制都应尽可能减少每个线程的空闲时间。
我将了解,如果使用ExecutorService,则每个线程都有自己的队列。但是,情况并非如此,因为队列由池中的不同线程共享...
任何澄清都将受到欢迎!
答案 0 :(得分:2)
假设您有大量的int数组,并且您想将所有整数都添加。使用ExecutorService
时,您可能会说:让我们将该数组划分为块,比如说线程数/4。因此,如果您有160个元素的数组(并且有4个CPU),则可以创建120 / 4 / 4 = 10
,因此您将创建16个块,每个块包含10个整数。创建可运行对象/可调用对象,并将其提交给执行服务(当然,还可以考虑在完成后合并这些结果的方法)。
现在,您希望每个CPU都承担其中4个任务并对其进行处理。现在,我们还假设其中一些数字的添加非常复杂(当然不是,但请忍受),结果可能是3个线程/ CPU完成了工作,而其中一个仅忙于第一个块。没有人希望这样做,但是可能会发生。现在的坏事是您对此无能为力。
ForkJoinPool
的意思是为我提供了您想如何拆分任务和实现以最小的工作量来完成的工作,其余的工作将由我来处理。在Stream API
中,这是通过Spliterator
来完成的;主要使用两种方法trySplit
(返回null
意味着不能再拆分或新的Spliterator
-意味着新的块)和forEachRemaning
无法再拆分您的任务了。这就是偷工作将为您提供帮助的地方。
您说如何计算您的块(通常分为两半)以及无法再拆分时该怎么做。 ForkJoinPool
将向所有线程分派第一个块,当其中一些空闲时-完成工作后,他们可以从其他线程查询其他队列,看看是否有工作。如果他们注意到某些其他线程队列中有块,则将它们拿走,自行拆分它们并对其进行处理。甚至可以发现,他们并没有自己完成所有工作,其他线程现在可以查询该线程的队列,并注意到仍有工作要做,等等。现在,当这3个线程空闲时,他们可以进行其他工作-他们都很忙。
此示例经过了一些简化,但与实际情况相差不远。只是您需要比CPU /线程更多的块才能进行工作窃取。因此,trySplit
通常必须有一个聪明的实现,并且流源中需要很多元素。