Почему ExecutorService не выключается после того, как invokeAny возвращает результат?

Когда я экспериментировал с Future и Callable в Java, у меня был результат, который я не могу понять с моим нынешним пониманием. Приведенный ниже сценарий предназначен только для демонстрационных целей.

Насколько я понимаю, когда набор Callable отправляется в ExecutorService через вызов invokeAny(), ExecutorService возвращает первый полученный результат. После получения результата я вызываю executorService.shutDown(), который, как я ожидал, закроет executorService, поскольку он уже возвращает результат. Однако для приведенного ниже сценария программа останавливается. Я также пытался вызвать executorService.shutDownNow(), но безуспешно.

Я пробовал тот же пример, используя два метода, которые усыпляют свой поток только на 1000 и 5000 секунд соответственно, и после того, как первый из них заснул, executorService.shutDown() работал, как и ожидалось.

Мое единственное предположение состоит в том, что shutDown() или shutDownNow() не могут прервать уже выполняющийся поток, как это делает cancel(mayInterruptIfRunning=true).

Может кто-нибудь объяснить, что здесь происходит, я правильно понимаю?

Вот пример:

public class InvokeAnyExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        int N = 50; // If I set N = 10, program exits successfully.

        ExecutorService executorService = Executors.newFixedThreadPool(2);

        Callable<Long> naiveFibonacci = () -> {
            log("Naive Fibonacci");
            long result = fibonacciNaive(N);
            log("Naive Fibonacci Finished");
            return result;
        };

        Callable<Long> optimizedFibonacci = () -> {
            log("Optimized Fibonacci");
            long[] cache = new long[1000];
            Arrays.fill(cache, -1);
            long result = fibonacciOptimized(N, cache);
            log("Optimized Fibonacci Finished");
            return result;
        };

        Long result = executorService.invokeAny(Arrays.asList(naiveFibonacci, optimizedFibonacci), 5, TimeUnit.SECONDS);
        log(String.valueOf(result));

        executorService.shutdown();
//        executorService.shutdownNow();
    }

    private static long fibonacciNaive(int n) {
        if (n == 0 || n == 1) return n;

        return fibonacciNaive(n - 1) + fibonacciNaive(n - 2);
    }

    private static long fibonacciOptimized(int n, long[] cache) {
        if (n == 0 || n == 1) return n;
        if (cache[n] != -1) return cache[n];

        long result = fibonacciOptimized(n - 1, cache) + fibonacciOptimized(n - 2, cache);
        cache[n] = result;

        return result;
    }

    private static void log(String message) {
        String prefix = Thread.currentThread().getName();
        System.out.println(String.format("In Thread (%s) : %s", prefix, message));
    }
}

Вот результат:

In Thread (pool-1-thread-2) : Optimized Fibonacci
In Thread (pool-1-thread-1) : Naive Fibonacci
In Thread (pool-1-thread-2) : Optimized Fibonacci Finished
In Thread (main) : 12586269025
--- PROGRAM HALTS

Что вы имеете в виду под "остановкой программы"? В любом случае, если ни одна из ваших задач не может быть прервана, они будут продолжать выполняться до тех пор, пока не будут выполнены. Отмена, выполненная invokeAny, не будет иметь никакого эффекта, если она не выполнит операцию блокировки или если она активно не проверит Thread.interrupted() (или не сделает что-либо еще, что позволяет прерывание).

Mark Rotteveel 16.03.2022 14:14

@MarkRotteveel, словами «программа останавливается», я пытался сказать, что «служба исполнителя не может быть отключена, хотя я вызываю shutDownNow() в конце». Это хороший момент для дальнейшего изучения, мне нужно выяснить, что делает задачу прерываемый.

recepinanc 16.03.2022 23:03

@SolomonSlow, на самом деле я не ожидал invokeAny() завершения работы, я ожидал, что после запуска invokeAny() я смогу запустить executorService.shutDown() и программа завершится успешно, но она не заканчивается. Я ожидал этого, потому что я понял, что invokeAny() возвращает первый результат, поэтому можно было отменить любую оставшуюся задачу и закрыть исполнителя, вызвав на нем shutDown().

recepinanc 16.03.2022 23:05

Хорошо, я неправильно понял.

Solomon Slow 17.03.2022 03:22
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
4
44
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как написал Марк Роттевел в своем комментарии:

Задача naiveFibonacci не может быть прервана. Он не вызывает никаких методов, проверяющих прерванное состояние потока, и сам метод fibonacciNaive() не проверяет прерванное состояние потока.

Если вы хотите сделать метод fibonacciNaive() прерываемым, вы можете изменить его на:

private static long fibonacciNaive(int n) throws InterruptedException {
    if (n == 0 || n == 1) return n;

    if (Thread.interrupted()) {
        log("interrupt detected");
        throw new InterruptedException();
    }

    return fibonacciNaive(n - 1) + fibonacciNaive(n - 2);
}

И с этим изменением задача naiveFibonacci будет остановлена, как только optimizedFibonacci даст результат.

Изменено: как прокомментировал Хольгер, использование Thread.interrupted() предпочтительнее Thread.currentThread().isInterrupted(), поскольку код в операторе if обрабатывает прерывание.

Большое спасибо, это имеет большой смысл! Мне не хватало той части, что «не все можно прервать». И спасибо, что показали мне, как сделать его прерываемым.

recepinanc 16.03.2022 23:07

Другие вопросы по теме