6

我正在比较一个测试程序的两个变体。两者都ForkJoinPool在具有四个内核的机器上使用 4 线程运行。

在“模式 1”中,我非常像使用执行器服务一样使用池。我把一堆任务扔进ExecutorService.invokeAll. 与普通的固定线程执行器服务相比,我获得了更好的性能(即使有对 Lucene 的调用,在那里执行一些 I/O)。

这里没有分而治之。从字面上看,我愿意

ExecutorService es = new ForkJoinPool(4);
es.invokeAll(collection_of_Callables);

在“模式 2”中,我将单个任务提交到池中,并在该任务中调用 ForkJoinTask.invokeAll 来提交子任务。所以,我有一个继承自 的对象,RecursiveAction它被提交到池中。在该类的计算方法中,我调用了来自另一个invokeAll类的对象集合,该类也继承自. 出于测试目的,我一次只提交一个第一个对象。我天真地期望看到所有四个线程都忙的事情,因为线程调用会为自己抓取其中一个子任务,而不是仅仅坐着阻塞。我能想到一些为什么它可能无法以这种方式工作的原因。RecursiveActioninvokeAll

在 VisualVM 中观察,在模式 2 中,一个线程几乎总是在等待。我期望看到的是调用invokeAll 的线程会立即处理其中一个被调用的任务,而不是静止不动。这肯定比使用普通线程池尝试这种方案所导致的死锁要好,但是,怎么办?如果提交其他内容,它是否会阻止一个线程?如果是这样,为什么模式 1 中没有同样的问题?

到目前为止,我一直在使用添加到 java 1.6 的引导类路径中的 jsr166 jar 来运行它。

4

2 回答 2

3

Fork Join 池的经典用途invokeAll是分叉一个任务并计算另一个任务(在该执行线程中)。不分叉的线程将在计算后加入。工作窃取伴随着这两个任务计算。当每个任务计算时,它预计会分叉它自己的子任务(直到达到某个阈值)。

我不确定为您调用了什么 invokeAllRecursiveAction.compute()但如果它是需要两个 RecursiveAction 的 invokeAll ,它将分叉一个,计算另一个并等待分叉的任务完成。

这与普通的 executor 服务不同,因为 ExecutorService 的每个任务只是队列上的 Runnable。ExecutorService 的两个任务不需要知道另一个任务的结果。这是 FJ 池的主要用例。

于 2012-03-15T03:03:14.033 回答
3

ForkJoinTask.invokeAll 正在分叉所有任务,但是是列表中的第一个。它自己运行的第一个任务。然后它加入其他任务。它的线程不会以任何方式释放到池中。所以你所看到的,它是对其他任务的线程阻塞才能完成。

于 2012-03-14T18:24:04.477 回答