Когда я экспериментировал с 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
@MarkRotteveel, словами «программа останавливается», я пытался сказать, что «служба исполнителя не может быть отключена, хотя я вызываю shutDownNow()
в конце». Это хороший момент для дальнейшего изучения, мне нужно выяснить, что делает задачу прерываемый.
@SolomonSlow, на самом деле я не ожидал invokeAny()
завершения работы, я ожидал, что после запуска invokeAny()
я смогу запустить executorService.shutDown()
и программа завершится успешно, но она не заканчивается. Я ожидал этого, потому что я понял, что invokeAny()
возвращает первый результат, поэтому можно было отменить любую оставшуюся задачу и закрыть исполнителя, вызвав на нем shutDown()
.
Хорошо, я неправильно понял.
Как написал Марк Роттевел в своем комментарии:
Задача 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
обрабатывает прерывание.
Большое спасибо, это имеет большой смысл! Мне не хватало той части, что «не все можно прервать». И спасибо, что показали мне, как сделать его прерываемым.
Что вы имеете в виду под "остановкой программы"? В любом случае, если ни одна из ваших задач не может быть прервана, они будут продолжать выполняться до тех пор, пока не будут выполнены. Отмена, выполненная
invokeAny
, не будет иметь никакого эффекта, если она не выполнит операцию блокировки или если она активно не проверитThread.interrupted()
(или не сделает что-либо еще, что позволяет прерывание).