Я добавил временные метки к выводу вашей программы, а также ловушку для сокращения, чтобы также можно было регистрировать завершение JVM:
0s Hello from thread: main # `main` method starts
0s I am in the thread: pool-1-thread-1 # `Runnable` submitted to the executor starts
0s I am dying ... # `main` method exits
50s I am done, and my value is 6 # `Runnable` submitted to the executor finishes
110s exiting # JVM process exits
Потоки, не являющиеся демонами
Причина, по которойпроцесс продолжается после выхода из метода main
, так как JVM должна дождаться завершения всех потоков, не являющихся демонами, прежде чем завершит работу . Исполнители, созданные с использованием класса Executors , создают потоки, не являющиеся демонами, по умолчанию (см. Executors.defaultThreadFactory () метод javadoc).
Настройка ThreadFactory
Youможет переопределить способ создания потоков, передав пользовательский ThreadFactory методу Executors.newCachedThreadPool () :
ExecutorService executorService = Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t;
});
executorService
будет иметь только потоки демоновв пуле потоков.
Кэширование потоков
Но обратите внимание, что JVM по-прежнему не завершает свою работу в течение 60 секунд после выполнения блока thenAccept
:
50s I am done, and my value is 6 # `Runnable` submitted to the executor finishes
110s exiting # JVM process exits
Почему это? Это объясняется в Executors.newCachedThreadPool () документации:
Создает пул потоков, который создает новые потоки по мере необходимости, но будет повторно использовать ранее созданные потоки, когда они станут доступны. Эти пулы обычно улучшают производительность программ, которые выполняют много кратковременных асинхронных задач. Вызовы для выполнения будут повторно использовать ранее созданные потоки, если они доступны. Если существующий поток недоступен, новый поток будет создан и добавлен в пул. Потоки, которые не использовались в течение шестидесяти секунд, прерываются и удаляются из кэша. Таким образом, пул, который остается бездействующим достаточно долго, не будет потреблять никаких ресурсов. Обратите внимание, что пулы с похожими свойствами, но с разными деталями (например, параметры времени ожидания) могут быть созданы с помощью конструкторов ThreadPoolExecutor.
Это означает, что этот пул потоков не удаляет потоки сразу после того, как они закончили выполнять запланированныезадачи. Вместо этого будет лучше использовать ранее созданные потоки для новых задач. Это является причиной задержки: после завершения вашей задачи поток остается в пуле потоков для повторного использования и только после следующих 60 секунд он уничтожается (вы отправляете только одну задачу в своей программе). Только тогда JVM может выйти (поскольку поток не является потоком демона, как указано выше).
Завершение работы ExecutorService
Обычно при работе с ExecutorService вам следуетВыключите его явно до завершения процесса. Для этого используйте методы ExecutorService.shutdown () или ExecutorService.shutdownNow () . Обратитесь к документации для различия между этими двумя.
Ссылки
Что такое поток демона в Java?
Включение службы ExecutorServiceдемону в Java
Программа не завершает работу сразу после выполнения всех задач ExecutorService
Модифицированная программа с метками времени и журналом завершения JVM:
public class Main {
private static final Instant start = Instant.now();
private static void debug(String message) {
System.out.println(Duration.between(start, Instant.now()).getSeconds() + "s " + message);
}
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> debug("exiting")));
debug("Hello from thread: "+Thread.currentThread().getName());
new Game().run();
debug("I am dying ... ");
}
static class Game {
public void run() {
value();
}
private int value() {
int number = 0;
CompletionStage<Void> c = calculate().thenApply(i -> i + 3).thenAccept(i -> debug("I am done, and my value is " + i));
return number;
}
private CompletionStage<Integer> calculate() {
CompletionStage<Integer> completionStage = new CompletableFuture<>();
Executors.newCachedThreadPool().submit(() -> {
debug("I am in the thread: " + Thread.currentThread().getName());
try {
Thread.sleep(50000);
((CompletableFuture<Integer>) completionStage).complete(3);
} catch (Exception e) {
e.printStackTrace();
}
return null;
});
return completionStage;
}
}
}