Я делаю асинхронный метод, который создает новый поток и, собственно, выполняет метод асинхронно. Распараллеливаю поток с помощью класса CompletableFuture.
Столкнулся с такой проблемой, что если асинхронный метод не успевает завершиться раньше главного потока, то при завершении главного потока, принудительно завершается и мой асинхронный метод. Конкретно не работает при исполнении в обычных классах.
Пробовал на servlet'ах, асинхронный метод успешно доходит до своего логического завершения вне зависимости от главного потока.
Если сталкивались с таковым, с радостью почитаю ваши решения и предложения.
Возможно, будет понятнее, если я приведу пример. Мой асинхронный метод тратит на обработку 20 секунд, но главный метод заканчивается раньше и обрубает работу асинхронного.
public class SomeClass {
public static void main(String[] args) {
SomeClass.waitAsync();
System.out.println("main thread finished");
}
public static CompletableFuture
CompletableFuture
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
CompletableFuture
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
CompletableFuture
return completableFuture;
}
В данном случае программа дождётся завершения задачи и завершится.
Вообще, тут достаточно много нюансов. Боюсь, в одном вопросе/ответе всё не охватить.
Комментариев нет:
Отправить комментарий