Почему ForkJoinPool:: invoke() блокирует основной поток?

отказ от ответственности: это первый раз, когда я использую платформу Fork-Join Java, поэтому я не на 100% уверен, что использую ее правильно. Java также не является моим основным языком программирования, поэтому это также может быть актуальным.


учитывая следующее SSCCE:

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;

class ForkCalculator extends RecursiveAction
{
    private final Integer[] delayTasks;

    public ForkCalculator(Integer[] delayTasks)
    {
        this.delayTasks = delayTasks;
    }

    @Override
    protected void compute()
    {
        if (this.delayTasks.length == 1) {
            this.computeDirectly();
            return;
        }

        Integer halfway = this.delayTasks.length / 2;

        ForkJoinTask.invokeAll(
            new ForkCalculator(
                Arrays.copyOfRange(this.delayTasks, 0, halfway)
            ),
            new ForkCalculator(
                Arrays.copyOfRange(this.delayTasks, halfway, this.delayTasks.length)
            )
        );
    }

    private void computeDirectly()
    {
        Integer delayTask = this.delayTasks[0];

        try {
            Thread.sleep(delayTask);
        } catch (InterruptedException ex) {
            System.err.println(ex.getMessage());
            System.exit(2);
        }

        System.out.println("Finished computing task with delay " + delayTask);
    }
}

public final class ForkJoinBlocker
{
    public static void main(String[] args)
    {
        ForkCalculator calculator = new ForkCalculator(
            new Integer[]{1500, 1400, 1950, 2399, 4670, 880, 5540, 1975, 3010, 4180, 2290, 1940, 510}
        );

        ForkJoinPool pool = new ForkJoinPool(
            Runtime.getRuntime().availableProcessors()
        );

        pool.invoke(calculator);

        //make it a daemon thread
        Timer timer = new Timer(true);

        timer.scheduleAtFixedRate(
            new TimerTask() {
                @Override
                public void run()
                {
                    System.out.println(pool.toString());
                }
            },
            100,
            2000
        );
    }
}

поэтому я создаю ForkJoinPool которому я представляю некоторые задачи, которые выполняют некоторую обработку. Я заменил их на Thread.sleep() для целей этого примера, чтобы сохранить его простым.

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

однако я заметил кое-что, чего не ожидал: в моем примере вывод примерно такой:

Finished computing task with delay 1500
Finished computing task with delay 2399
Finished computing task with delay 1400
Finished computing task with delay 4180
Finished computing task with delay 1950
Finished computing task with delay 5540
Finished computing task with delay 880
.......

что означает, что" status-task " никогда не выполняется.

однако, если я изменю свой код, чтобы переместить pool.invoke(calculator); в самом конце, то он работает как ожидалось:

java.util.concurrent.ForkJoinPool@59bf63ba[Running, parallelism = 4, size = 4, active = 4, running = 0, steals = 0, tasks = 5, submissions = 0]
Finished computing task with delay 1500
java.util.concurrent.ForkJoinPool@59bf63ba[Running, parallelism = 4, size = 4, active = 4, running = 0, steals = 0, tasks = 5, submissions = 0]
Finished computing task with delay 2399
Finished computing task with delay 1400
java.util.concurrent.ForkJoinPool@59bf63ba[Running, parallelism = 4, size = 4, active = 4, running = 0, steals = 0, tasks = 4, submissions = 0]
Finished computing task with delay 4180
Finished computing task with delay 1950
......

единственный вывод, который я могу сделать, это ForkJoinPool::invoke() блокирует основной поток (он возвращается только после завершения всех задач в пуле).

Я ожидал, что код в основном потоке будет продолжать выполняться, в то время как задачи в пуле Fork-join обрабатываются асинхронно.

мой вопрос - это: это происходит потому, что я неправильно использовал фреймворк? Есть ли что-то, что я должен исправить в моем код?

я заметил, что один из ForkJoinPoolконструкторы s имеют boolean asyncMode параметр, но, из того, что я могу сказать из реализации, это просто решить между FIFO_QUEUE и LIFO_QUEUE режимы выполнения (не совсем уверен, что это такое):

public ForkJoinPool(
    int parallelism,
    ForkJoinWorkerThreadFactory factory,
    UncaughtExceptionHandler handler,
    boolean asyncMode
) {
    this(checkParallelism(parallelism),
         checkFactory(factory),
         handler,
         asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
         "ForkJoinPool-" + nextPoolId() + "-worker-");
    checkPermission();
}

1 ответов


в принципе invoke() будет ждать завершения всей задачи перед возвратом, поэтому да, основной поток блокируется. После этого Timer не имеет времени для выполнения, потому что он работает в потоке демона.

вы можете просто использовать execute() вместо invoke() который запускает задачу асинхронно. Тогда вы можете join() на ForkJoinTask дождаться результата, во время которого Timer будет:

ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
pool.execute(calculator);

    //make it a daemon thread
Timer timer = new Timer(true);

timer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            System.out.println(pool.toString());
        }
    }, 100, 2000);

calculator.join(); // wait for computation