Страницы

Поиск по вопросам

понедельник, 19 ноября 2018 г.

Как можно обойти принудительное завершение параллельного потока после завершения главного JAVA

Я делаю асинхронный метод, который создает новый поток и, собственно, выполняет метод асинхронно. Распараллеливаю поток с помощью класса CompletableFuture.
Столкнулся с такой проблемой, что если асинхронный метод не успевает завершиться раньше главного потока, то при завершении главного потока, принудительно завершается и мой асинхронный метод. Конкретно не работает при исполнении в обычных классах.
Пробовал на servlet'ах, асинхронный метод успешно доходит до своего логического завершения вне зависимости от главного потока.
Если сталкивались с таковым, с радостью почитаю ваши решения и предложения.
Возможно, будет понятнее, если я приведу пример. Мой асинхронный метод тратит на обработку 20 секунд, но главный метод заканчивается раньше и обрубает работу асинхронного.
public class SomeClass {
public static void main(String[] args) { SomeClass.waitAsync(); System.out.println("main thread finished"); }
public static CompletableFuture waitAsync() {
CompletableFuture completableFuture = CompletableFuture .supplyAsync(() -> threadWait());
return completableFuture;
}
public static boolean threadWait() {
try { System.out.println("Async method started"); Thread.sleep(20000); System.out.println("Async method finished"); return true; } catch (InterruptedException e) { e.printStackTrace(); return false; } }
}
Таким образом, в консоль выведется:
Async method started main thread finished


Ответ

Вся проблема в том, что CompletableFuture supplyAsync(Supplier supplier) использует внутри себя ForkJoinPool.commonPool(). [https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#supplyAsync-java.util.function.Supplier-][1] А вот джавадок для ForkJoinPool.commonPool() https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ForkJoinPool.html#commonPool-- То есть всё умирает, когда кончается метод main. Добавьте логирование в вашу лямбду, и вы увидите, что внутри supplyAsync(Supplier supplier) орудует daemon-тред.
.supplyAsync(() -> { System.out.println("Current thread is daemon: " + Thread.currentThread().isDaemon()); return threadWait(); });
Получим:
Current thread is daemon: true Async method started main thread finished
Так как JVM сворачивается, не дожидаясь завершения daemon-тредов, очевидно, что ждать завершения работы в supplyAsync в данном случае никто не будет.
Выход - использовать перегруженный метод supplyAsync(Supplier supplier, Executor executor), передав ему какой-нибудь экзекьютор, который оперирует non-daemon тредами, например:
public static CompletableFuture waitAsync() {
CompletableFuture completableFuture = CompletableFuture .supplyAsync(() -> { System.out.println("Current thread is daemon: " + Thread.currentThread().isDaemon()); return threadWait();}, Executors.newSingleThreadExecutor());
return completableFuture;
}
Аутпут:
Current thread is daemon: false Async method started main thread finished Async method finished
UPD Как совершенно справедливо отметил @Artem Konovalov, если мы передаём в метод свой Executor и просто забываем о нём, программа никогда не завершится самостоятельно, т.к. треды в пуле сами не умрут. Поэтому необходимо сохранить ссылку на передаваемый ExecutorService и завершить его работу самостоятельно. Например, так:
public static void main(String[] args) { ExecutorService executorService =Executors.newSingleThreadExecutor(); waitAsync(executorService); executorService.shutdown(); System.out.println("main thread finished"); }
public static CompletableFuture waitAsync(ExecutorService executor) {
CompletableFuture completableFuture = CompletableFuture .supplyAsync(() -> threadWait(), executor);
return completableFuture;
}
В данном случае программа дождётся завершения задачи и завершится. Вообще, тут достаточно много нюансов. Боюсь, в одном вопросе/ответе всё не охватить.

Комментариев нет:

Отправить комментарий