我正在比较测试程序的两个变体。两者都在具有四个核心的计算机上使用4线程ForkJoinPool
进行操作。
在'模式1'中,我使用池非常像执行程序服务。我将一堆任务扔进ExecutorService.invokeAll
。我获得了比普通的固定线程执行程序服务更好的性能(即使有调用Lucene,在那里执行一些I / O)。
这里没有分而治之。从字面上看,我做了
ExecutorService es = new ForkJoinPool(4);
es.invokeAll(collection_of_Callables);
在'模式2'中,我向池中提交一个任务,并在该任务中调用ForkJoinTask.invokeAll来提交子任务。所以,我有一个继承自RecursiveAction
的对象,它被提交到池中。在该类的compute方法中,我对来自不同类的对象集合调用invokeAll
,该类也继承自RecursiveAction
。出于测试目的,我只提交第一个对象的一次。我天真地期望看到所有四个线程忙什么,因为调用invokeAll
的线程会为自己抓取一个子任务而不仅仅是坐着和阻塞。我可以想到为什么它可能不会那样工作的原因。
在VisualVM中观察,在模式2中,一个线程几乎总是在等待。我期望看到的是调用invokeAll的线程立即开始处理其中一个被调用的任务,而不仅仅是静坐。这肯定比使用普通线程池尝试此方案所导致的死锁更好,但仍然是什么?它是否保留一个线程,以防其他东西被提交?如果是这样,为什么模式1中出现同样的问题?
到目前为止,我一直使用添加到java 1.6引导类路径的jsr166 jar来运行它。
答案 0 :(得分:3)
ForkJoinTask.invokeAll正在分配所有任务,但列表中的第一个任务。它自己运行的第一个任务。然后它加入其他任务。它的线程不会以任何方式释放到池中。所以你所看到的,它是完成其他任务的线程阻塞。
答案 1 :(得分:3)
对于Fork Join池,invokeAll
的经典用法是分叉一个任务并计算另一个任务(在该执行线程中)。不计分叉的线程将在计算后加入。工作窃取涉及两个任务计算。当每个任务计算时,它应该分叉它自己的子任务(直到满足某个阈值)。
我不确定你的RecursiveAction.compute()
调用了什么invokeAll但是如果它是带有两个RecursiveAction的invokeAll,它将分叉一个,计算另一个并等待分叉任务完成。
这与普通的执行程序服务不同,因为ExecutorService的每个任务都只是队列中的Runnable。 ExecutorService不需要两个任务来知道另一个任务的结果。这是FJ Pool的主要用例。