Я сравниваю два варианта тестовой программы. Оба работают с 4-нитью ForkJoinPool
на машине с четырьмя ядрами.
В «режиме 1» я использую пул очень похоже на службу исполнителя. Я бросаю кучу задач в ExecutorService.invokeAll
. Я получаю лучшую производительность, чем от обычной службы исполнения с фиксированными потоками (даже при том, что там есть вызовы Lucene, которые выполняют некоторые операции ввода-вывода).
Здесь нет разделяй и властвуй. Буквально я делаю
ExecutorService es = new ForkJoinPool(4);
es.invokeAll(collection_of_Callables);
В «режиме 2» я отправляю одну задачу в пул, и в этой задаче вызывается ForkJoinTask.invokeAll для отправки подзадач. Итак, у меня есть объект, который наследуется от RecursiveAction
, и он передается в пул. В методе вычисления этого класса я вызываю invokeAll
для коллекции объектов из другого класса, который также наследуется от RecursiveAction
. В целях тестирования я отправляю только по одному из первых объектов. То, что я наивно ожидал увидеть, что все четыре потока заняты, так как поток, вызывающий invokeAll
, будет сам брать одну из подзадач вместо того, чтобы просто сидеть и блокировать. Я могу подумать о некоторых причинах, по которым это может не сработать.
При просмотре в VisualVM в режиме 2 один поток почти всегда ожидает. Я ожидаю увидеть поток, вызывающий invokeAll, который сразу же начнет работать над одной из вызванных задач, а не просто сидеть на месте. Это, безусловно, лучше, чем взаимоблокировки, которые могут возникнуть при попытке использовать эту схему с обычным пулом потоков, но все же, что случилось? Он удерживает один поток на случай, если что-то еще будет отправлено? И если да, то почему не та же проблема в режиме 1?
До сих пор я запускал это, используя jsr166 jar, добавленный в путь к загрузочному классу java 1.6.