ThreadPoolExecutor vs ForkJoinPool:窃取子任务

ThreadPoolExecutor vs ForkJoinPool: stealing subtasks

来自 java 文档,

A ForkJoinPool differs from other kinds of ExecutorService mainly by virtue of employing work-stealing: all threads in the pool attempt to find and execute subtasks created by other active tasks (eventually blocking waiting for work if none exist).

This enables efficient processing when most tasks spawn other subtasks (as do most ForkJoinTasks). When setting asyncMode to true in constructors, ForkJoinPools may also be appropriate for use with event-style tasks that are never joined.

经过下面ForkJoinPool example,与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 池的行为有何不同?

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

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

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

这些新任务不是提供给池队列,而是提供给特定于线程的任务。因此,它们 taken/pulled 优先于第一个池,就好像您已经完成了同一任务中的所有工作一样。此外,调用者任务似乎因子任务完成而被阻止。

实际上,"blocked time"是用来消费子任务的。让其他线程 "to loaf around" 而其中一个线程被工作淹没将是愚蠢的。所以,"work stealing"发生了。

超越。为了高效,"work stealing"takes/pulls任务从相反的界线开始。这大大减少了对队列写入的争用。

始终保持效率,最好只将问题拆分为两个子任务,并让子任务一次又一次地拆分。即使你知道问题也必须直接拆分成N个部分。这是因为 "work stealing" 需要并发写入共享资源,因此限制其激活和争用!