为什么当有一个尚未完成的完成阶段时主线程不终止?

Jac*_*son 1 java multithreading threadpool completable-future completion-stage

这是我的简单代码:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

执行的输出为:

Hello from thread: main
I am in the thread: pool-1-thread-1
I am dying ... 
Run Code Online (Sandbox Code Playgroud)

但问题是:主线程不会立即终止,它会等待 50000 毫秒。这是我的问题。我知道它应该终止,因为没有更多的事情要执行。

最初我认为原因是“睡眠”正在主线程中执行,这就是为什么我打印了线程的名称,它们是两个不同的线程。

感谢帮助。

jan*_*nis 6

我在程序的输出中添加了时间标记,还添加了一个关闭挂钩,以便也可以记录 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
Run Code Online (Sandbox Code Playgroud)

非守护线程

方法退出后进程继续的原因mainJVM需要等待所有非守护线程终止才关闭。使用Executors类生成的执行器默认创建非守护线程(请参阅Executors.defaultThreadFactory()方法 javadoc)。

自定义ThreadFactory

您可以通过将自定义ThreadFactory传递给Executors.newCachedThreadPool()方法来覆盖线程的创建方式:

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

executorService其线程池中只有守护线程。

线程缓存

thenAccept但请注意,在执行块后 60 秒内 JVM 仍然不会退出:

50s I am done, and my value is 6        # `Runnable` submitted to the executor finishes
110s exiting                            # JVM process exits
Run Code Online (Sandbox Code Playgroud)

这是为什么?Executors.newCachedThreadPool()文档中对此进行了解释(添加了重点):

创建一个线程池,该线程池根据需要创建新线程,但会重用以前构造的线程(当它们可用时)。这些池通常会提高执行许多短期异步任务的程序的性能。对执行的调用将重用先前构造的线程(如果可用)。如果没有可用的现有线程,则会创建一个新线程并将其添加到池中。六十秒内未使用的线程将被终止并从缓存中删除。因此,保持空闲足够长的时间的池将不会消耗任何资源。请注意,可以使用 ThreadPoolExecutor 构造函数创建具有相似属性但不同细节(例如超时参数)的池。

这意味着该线程池不会在线程完成计划任务后立即释放它们。相反,它会尽力重用以前创建的线程来提交新任务。这就是延迟的原因:任务完成后,线程仍保留在线程池中以供重用,并且仅在接下来的 60 秒后才被销毁(您在程序中只提交了一项任务)。只有这样 JVM 才能退出(因为该线程不是上面指出的守护线程)。

关闭ExecutorService

通常,在使用 ExecutorService 时,应该在进程终止之前显式关闭它。为此,请使用ExecutorService.shutdown()ExecutorService.shutdownNow()方法。请参阅文档了解两者之间的区别。

参考

Java 中的守护线程是什么?

在 Java 中将 ExecutorService 转为守护进程

当所有 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

2320 次

最近记录:

5 年,11 月 前