Почему основной поток не завершается, когда есть этап завершения, который не был завершен? - PullRequest
0 голосов
/ 09 ноября 2019

Это мой простой код:

public class Main4 {
    public static void main(String[] args) {
        System.out.println("Hello from thread: "+Thread.currentThread().getName());
        new Game().run();
        System.out.println("I am dying ... ");
    }

    static class Game {
        public void run() {
            value();
        }

        private int value() {
            int number = 0;
            CompletionStage<Void> c = calculate().thenApply(i -> i + 3).thenAccept(i -> System.out.println("I am done, and my value is " + i));
            return number;
        }

        private CompletionStage<Integer> calculate() {
            CompletionStage<Integer> completionStage = new CompletableFuture<>();
            Executors.newCachedThreadPool().submit(() -> {
                System.out.println("I am in the thread: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(50000);
                    ((CompletableFuture<Integer>) completionStage).complete(3);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;

            });
            return completionStage;
        }
    }
}

Результат выполнения:

Hello from thread: main
I am in the thread: pool-1-thread-1
I am dying ... 

Но дело в том, что основной поток не прерывается сразу, он ждетза 50000 миллисекунд. это мой вопросЯ понял, что он должен завершиться, потому что больше не нужно ничего выполнять.

Изначально я думал, что причина в том, что "сон" выполняется в главном потоке, и поэтому я напечатал именатемы, и это две разные темы.

Помощь приветствуется.

Ответы [ 2 ]

3 голосов
/ 09 ноября 2019

Я добавил временные метки к выводу вашей программы, а также ловушку для сокращения, чтобы также можно было регистрировать завершение JVM:

0s Hello from thread: main              # `main` method starts
0s I am in the thread: pool-1-thread-1  # `Runnable` submitted to the executor starts
0s I am dying ...                       # `main` method exits
50s I am done, and my value is 6        # `Runnable` submitted to the executor finishes
110s exiting                            # JVM process exits

Потоки, не являющиеся демонами

Причина, по которойпроцесс продолжается после выхода из метода main, так как JVM должна дождаться завершения всех потоков, не являющихся демонами, прежде чем завершит работу . Исполнители, созданные с использованием класса Executors , создают потоки, не являющиеся демонами, по умолчанию (см. Executors.defaultThreadFactory () метод javadoc).

Настройка ThreadFactory

Youможет переопределить способ создания потоков, передав пользовательский ThreadFactory методу Executors.newCachedThreadPool () :

ExecutorService executorService = Executors.newCachedThreadPool(runnable -> {
    Thread t = new Thread(runnable);
    t.setDaemon(true);
    return t;
});

executorService будет иметь только потоки демоновв пуле потоков.

Кэширование потоков

Но обратите внимание, что JVM по-прежнему не завершает свою работу в течение 60 секунд после выполнения блока thenAccept:

50s I am done, and my value is 6        # `Runnable` submitted to the executor finishes
110s exiting                            # JVM process exits

Почему это? Это объясняется в Executors.newCachedThreadPool () документации:

Создает пул потоков, который создает новые потоки по мере необходимости, но будет повторно использовать ранее созданные потоки, когда они станут доступны. Эти пулы обычно улучшают производительность программ, которые выполняют много кратковременных асинхронных задач. Вызовы для выполнения будут повторно использовать ранее созданные потоки, если они доступны. Если существующий поток недоступен, новый поток будет создан и добавлен в пул. Потоки, которые не использовались в течение шестидесяти секунд, прерываются и удаляются из кэша. Таким образом, пул, который остается бездействующим достаточно долго, не будет потреблять никаких ресурсов. Обратите внимание, что пулы с похожими свойствами, но с разными деталями (например, параметры времени ожидания) могут быть созданы с помощью конструкторов ThreadPoolExecutor.

Это означает, что этот пул потоков не удаляет потоки сразу после того, как они закончили выполнять запланированныезадачи. Вместо этого будет лучше использовать ранее созданные потоки для новых задач. Это является причиной задержки: после завершения вашей задачи поток остается в пуле потоков для повторного использования и только после следующих 60 секунд он уничтожается (вы отправляете только одну задачу в своей программе). Только тогда JVM может выйти (поскольку поток не является потоком демона, как указано выше).

Завершение работы ExecutorService

Обычно при работе с ExecutorService вам следуетВыключите его явно до завершения процесса. Для этого используйте методы ExecutorService.shutdown () или ExecutorService.shutdownNow () . Обратитесь к документации для различия между этими двумя.

Ссылки

Что такое поток демона в Java?

Включение службы ExecutorServiceдемону в Java

Программа не завершает работу сразу после выполнения всех задач ExecutorService

Модифицированная программа с метками времени и журналом завершения JVM:

public class Main {
    private static final Instant start = Instant.now();

    private static void debug(String message) {
        System.out.println(Duration.between(start, Instant.now()).getSeconds() + "s " + message);
    }

    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> debug("exiting")));
        debug("Hello from thread: "+Thread.currentThread().getName());
        new Game().run();
        debug("I am dying ... ");
    }

    static class Game {
        public void run() {
            value();
        }

        private int value() {
            int number = 0;
            CompletionStage<Void> c = calculate().thenApply(i -> i + 3).thenAccept(i -> debug("I am done, and my value is " + i));
            return number;
        }

        private CompletionStage<Integer> calculate() {
            CompletionStage<Integer> completionStage = new CompletableFuture<>();
            Executors.newCachedThreadPool().submit(() -> {
                debug("I am in the thread: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(50000);
                    ((CompletableFuture<Integer>) completionStage).complete(3);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;

            });
            return completionStage;
        }
    }
}
1 голос
/ 09 ноября 2019

основной поток не завершается немедленно

Это потому, что вы создаете ExecutorService, который вы никогда не закрываете. Вот ваш код с включением выключения ExecutorService.

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main4 {
    public static void main(String[] args) {
        System.out.println("Hello from thread: "+Thread.currentThread().getName());
        new Game().run();
        System.out.println("I am dying ... ");
    }

    static class Game {
        public void run() {
            value();
        }

        private int value() {
            int number = 0;
            CompletionStage<Void> c = calculate().thenApply(i -> i + 3).thenAccept(i -> System.out.println("I am done, and my value is " + i));
            return number;
        }

        private CompletionStage<Integer> calculate() {
            CompletionStage<Integer> completionStage = new CompletableFuture<>();
            ExecutorService es = Executors.newCachedThreadPool();
            es.submit(() -> {
                System.out.println("I am in the thread: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(50000);
                    ((CompletableFuture<Integer>) completionStage).complete(3);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;

            });
            es.shutdown();
            return completionStage;
        }
    }
}

Выход из вышеприведенного кода: ...

Hello from thread: main
I am dying ... 
I am in the thread: pool-1-thread-1
I am done, and my value is 6
...