Страницы

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

понедельник, 23 декабря 2019 г.

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

#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 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; } В данном случае программа дождётся завершения задачи и завершится. Вообще, тут достаточно много нюансов. Боюсь, в одном вопросе/ответе всё не охватить.

Ответ 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; } ... }

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

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