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