Каждый рабочий поток в ForkJoinPool
имеет свою собственную рабочую очередь. Асинхронный режим касается порядка, в котором каждый работник выполняет разветвленные задачи, которые никогда не присоединяются из его очереди работы.
Рабочие в ForkJoinPool
в асинхронном режиме обрабатывать такие задачи в порядке FIFO (первым пришел, первым вышел).По умолчанию ForkJoinPool
s обрабатывает такие задачи в порядке LIFO (последний пришел, первый вышел).
Важно подчеркнуть, что параметр асинхронного режима касается только разветвленных задач, которые никогда не объединяются. При использовании ForkJoinPool
для того, для чего он изначально был разработан, а именно для рекурсивной декомпозиции форка / соединения, asyncMode
вообще не вступает в игру.Только когда работник не участвует в фактической обработке fork / join, он выполняет асинхронные задачи, и только тогда действительно запрашивается флаг asyncMode
.
Вот небольшая программа, которая демонстрирует разницу между двумя различными асинхроннымиНастройки режима:
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Demo of {@code ForkJoinPool} behaviour in async and non-async mode.
*/
public class ForkJoinAsyncMode {
public static void main(String[] args) {
// Set the asyncMode argument below to true or false as desired:
ForkJoinPool pool = new ForkJoinPool(
4, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
pool.invoke(new RecursiveRangeAction(0, 200));
pool.awaitQuiescence(2L, TimeUnit.SECONDS);
}
/**
* A {@code ForkJoinTask} that prints a range if the range is smaller than a
* certain threshold; otherwise halves the range and proceeds recursively.
* Every recursive invocation also forks off a task that is never joined.
*/
private static class RecursiveRangeAction extends RecursiveAction {
private static final AtomicInteger ASYNC_TASK_ID = new AtomicInteger();
private final int start;
private final int end;
RecursiveRangeAction(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end - start < 10) {
System.out.format("%s range [%d-%d] done%n",
Thread.currentThread().getName(), start, end);
} else {
int mid = (start + end) >>> 1;
int id = ASYNC_TASK_ID.incrementAndGet();
System.out.format(
"%1$s [%2$d-%3$d] -< [%3$d-%4$d], fork async task %5$d%n",
Thread.currentThread().getName(), start, mid, end, id);
// Fork off additional asynchronous task that is never joined.
ForkJoinTask.adapt(() -> {
System.out.format("%s async task %d done%n",
Thread.currentThread().getName(), id);
}).fork();
invokeAll(new RecursiveRangeAction(start, mid),
new RecursiveRangeAction(mid, end));
}
}
}
}
В не асинхронный режим (по умолчанию ForkJoinPool
) разветвленные задачи, которые никогда не объединяются, выполняются в порядке LIFO.
Когда вы запускаете пример программы в не асинхронном режиме, просматривая вывод одного работника, вы можете увидеть шаблон, подобный следующему:
ForkJoinPool-1-worker-0 [175-187] -< [187-200], fork async task 10
ForkJoinPool-1-worker-0 [175-181] -< [181-187], fork async task 11
ForkJoinPool-1-worker-0 range [175-181] done
ForkJoinPool-1-worker-0 range [181-187] done
ForkJoinPool-1-worker-0 [187-193] -< [193-200], fork async task 12
ForkJoinPool-1-worker-0 range [187-193] done
ForkJoinPool-1-worker-0 range [193-200] done
ForkJoinPool-1-worker-0 async task 12 done
ForkJoinPool-1-worker-0 async task 11 done
ForkJoinPool-1-worker-0 async task 10 done
Здесь задачи 10, 11, 12 разветвляются и затем выполняютсяв обратном порядке, как только работник приступает к их выполнению.
В асинхронном режиме , с другой стороны, снова при просмотре вывода одного работника шаблон будет выглядеть следующим образом:
ForkJoinPool-1-worker-3 [150-175] -< [175-200], fork async task 8
ForkJoinPool-1-worker-3 [150-162] -< [162-175], fork async task 9
ForkJoinPool-1-worker-3 [150-156] -< [156-162], fork async task 10
ForkJoinPool-1-worker-3 range [150-156] done
ForkJoinPool-1-worker-3 range [156-162] done
ForkJoinPool-1-worker-3 [162-168] -< [168-175], fork async task 11
...
ForkJoinPool-1-worker-3 async task 8 done
ForkJoinPool-1-worker-3 async task 9 done
ForkJoinPool-1-worker-3 async task 10 done
ForkJoinPool-1-worker-3 async task 11 done
Задачи 8, 9, 10, 11 разветвляются и затем выполняются в том порядке, в котором они были отправлены.
Когда использовать какой режим?Всякий раз, когда пул потоков ForkJoinPool
выбирается для использования преимуществ его свойств кражи работы, а не для обработки рекурсивных задач fork / join, асинхронный режим, вероятно, является более естественным выбором, поскольку задачи выполняются в порядке их отправки.*
Асинхронные управляемые событиями структуры, такие как CompletableFuture
, иногда говорят, что получают прибыль от асинхронного режима.Например, при построении сложной цепочки обратных вызовов CompletableFuture
пользовательский исполнитель ForkJoinPool
в асинхронном режиме может работать немного лучше, чем исполнитель по умолчанию.(Я не могу говорить по опыту.)