12

从java文档,

ForkJoinPool 与其他类型的 ExecutorService 的不同之处主要在于采用了工作窃取:池中的所有线程都尝试查找并执行由其他活动任务创建的子任务(如果不存在,则最终阻塞等待工作)。

当大多数任务产生其他子任务(大多数 ForkJoinTasks 也是如此)时,这可以实现高效处理。在构造函数中将 asyncMode 设置为 true 时,ForkJoinPools 也可能适用于从未加入的事件样式任务。

经过下面的ForkJoinPool 示例后,与 ThreadPoolExecutor 不同,我没有看到设置队列大小的参数。我不知道 ForkJoinPool 如何窃取机制。

//creating the ThreadPoolExecutor

ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 10, 60, TimeUnit.SECONDS, 
new ArrayBlockingQueue<Runnable>(3000), threadFactory, rejectionHandler);

假设我已经创建了 10 个线程的 ThreadPoolExecutor,并且已经提交了 3000 个 Callable 任务。这些线程如何分担子任务的执行负载?

对于相同的用例,ForkJoin 池的行为如何不同?

4

2 回答 2

17

如果您预先有 3000 个任务,并且它们不会产生其他任务,则两者的行为不会有本质上的不同:如果有 10 个线程,一次将运行 10 个任务,直到它们全部完成。

ForkJoinPool 是为您有一个或几个任务开始的情况而设计的,但这些任务知道如何将自己拆分为子任务。在这种情况下,ForkJoinPool 被优化以允许任务检查处理线程的可用性并适当地拆分它们自己。

于 2015-10-31T18:31:36.470 回答
5

ForkJoinPool中,有两种队列——一种是在提交任务时基本上使用的队列,另一种是特定于线程的队列(即每个线程一个队列)。从ForkJoinTask您可以调用新任务(通常是您的问题的拆分)。

这些新任务不提供给池队列,而是提供给线程特定的任务。因此,它们被优先获取/拉到池一,就好像您在同一个任务中完成了所有工作。此外,调用程序任务似乎被阻止以完成子任务。

实际上,“阻塞时间”用于消耗子任务。让其他线程“闲逛”而其中一个线程被工作淹没是愚蠢的。于是,“偷工减料”就发生了。

超越。为了提高效率,“工作窃取”从相反的边界获取/拉取任务。这大大减少了队列写入的争用。

始终在效率上,最好只将问题拆分为两个子任务,让子任务一次又一次地拆分。即使知道问题也必须直接拆分为 N 个部分。这是因为“工作窃取”需要并发写入共享资源,所以限制它的激活和争用!

于 2015-10-31T18:45:53.033 回答