Что такое асинхронный режим ForkJoinPool - PullRequest
11 голосов
/ 12 апреля 2011

Что означает асинхронный режим ForkJoinPool? В Javadoc упоминается, что он создает очереди (это очередь на поток?) Вместо FIFO. Что это означает на практике?

Ответы [ 2 ]

11 голосов
/ 10 января 2016

Каждый рабочий поток в 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 в асинхронном режиме может работать немного лучше, чем исполнитель по умолчанию.(Я не могу говорить по опыту.)

5 голосов
/ 31 января 2012

Он предназначен для задач в стиле событий, которые отправляются, но никогда не присоединяются. Таким образом, в основном задачи, которые выполняются для их побочных эффектов, а не для возврата результата, который будет обработан задачей разветвления после объединения.

...