为什么主线程在有未完成的完成阶段时不终止?

Why the main thread doesn't terminate when there is a completion stage that has not been completed?

这是我的简单代码:

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 毫秒。这是我的问题。我明白它应该终止,因为没有更多的事情要执行。

一开始我以为是"sleep"是在主线程中执行的,所以打印了线程的名字,是两个不同的线程

感谢帮助。

我在您的程序输出中添加了时间标记,还添加了一个关闭挂钩,以便也可以记录 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 class create non-daemon threads by default (see Executors.defaultThreadFactory() 方法生成的执行器 javadoc).

自定义 ThreadFactory

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

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

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

线程缓存

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

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

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

Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks. Calls to execute will reuse previously constructed threads if available. If no existing thread is available, a new thread will be created and added to the pool. Threads that have not been used for sixty seconds are terminated and removed from the cache. Thus, a pool that remains idle for long enough will not consume any resources. Note that pools with similar properties but different details (for example, timeout parameters) may be created using ThreadPoolExecutor constructors.

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

正在关闭 ExecutorService

通常在使用 ExecutorService you should shut it down explicitly before the process is terminated. For this purpose use ExecutorService.shutdown() or ExecutorService.shutdownNow() 方法时。这两者的区别参考文档

参考资料

What is a daemon thread in Java?

Turning an ExecutorService to daemon in Java

Program does not terminate immediately when all ExecutorService tasks are done

带有时间标记和 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;
        }
    }
}

主线程没有立即终止

那是因为您创建了一个您从未关闭的 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