#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
Ответы
Ответ 1
Вся проблема в том, что 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 CompletableFuturewaitAsync() { 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; } В данном случае программа дождётся завершения задачи и завершится. Вообще, тут достаточно много нюансов. Боюсь, в одном вопросе/ответе всё не охватить. Ответ 2
Вижу следующее решение данной проблемы - сохранять все CompletableFuture и проверить их на завершенность перед завершением главного потока. Примерно так: public class Main { private final Set> futures = Collections.newSetFromMap(new ConcurrentHashMap<>()); public static void main(String[] args) { Main main = new Main(); main.waitAsync(); System.out.println("main thread finished"); main.futures.forEach(CompletableFuture::join); } public CompletableFuture waitAsync() { CompletableFuture completableFuture = CompletableFuture.supplyAsync(Main::threadWait); futures.add(completableFuture); completableFuture.whenComplete((e, v) -> futures.remove(completableFuture)); return completableFuture; } ... }
Комментариев нет:
Отправить комментарий