ForkJoinPool似乎浪费了一个线程

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

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

这里没有分而治之。 从字面上看,我做到了

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

在’模式2’中,我向池中提交单个任务,并在该任务中调用ForkJoinTask.invokeAll来提交子任务。 所以,我有一个inheritance自RecursiveAction的对象,它被提交到池中。 在该类的compute方法中,我在来自不同类的对象集合上调用invokeAll ,该类也inheritance自RecursiveAction 。 出于测试目的,我只提交第一个对象的一次一个。 我天真地期望看到所有四个线程忙什么,因为调用invokeAll的线程会为自己获取一个子任务而不仅仅是坐和阻塞。 我可以想到为什么它可能不会那样工作的一些原因。

在VisualVM中观察,在模式2中,一个线程几乎总是在等待。 我希望看到的是调用invokeAll的线程立即开始处理其中一个被调用的任务,而不仅仅是静坐。 这肯定比使用普通线程池尝试此方案所导致的死锁更好,但仍然是什么? 它是否保留一个线程,以防其他东西被提交? 而且,如果是这样,为什么模式1中的问题不一样?

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

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

对于Fork Join池, invokeAll的经典用法是分叉一个任务并计算另一个任务(在该执行线程中)。 不计分叉的线程将在计算后加入。 工作窃取涉及两个任务计算。 当每个任务计算时,它应该分叉它自己的子任务(直到满足某个阈值)。

我不确定你的RecursiveAction.compute()调用了什么invokeAll但是如果它是带有两个RecursiveAction的invokeAll,它将fork一个,计算另一个并等待forked任务完成。

这与普通执行程序服务不同,因为ExecutorService的每个任务都只是队列中的Runnable。 ExecutorService不需要两个任务来知道另一个任务的结果。 这是FJ Pool的主要用例。