即使在 main 函数退出后,这个 java 程序如何继续运行?

Bha*_*nda 1 java concurrency

我正在尝试学习java的并发API。下面是一个示例程序。

    class WaitTest {
    public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        ExecutorService executorService = null;
        try {
            executorService = Executors.newSingleThreadExecutor();
            Future<?> future = executorService.submit(() ->
                {
                    for (int i = 0; i < 100; i++) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("Printing " + i);
                    }
                });
            future.get(5, TimeUnit.SECONDS);
            System.out.println("Reached successfully");
        } finally {
            if (executorService != null) {
                executorService.shutdown();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

提供给 ExecutorService 的 Runnable 任务需要 10 秒才能完成。我已将超时设置为 5 秒以从未来对象中获取结果。所以很明显,由于抛出了 TimeoutException,main 方法在 5 秒后退出。但是即使在 main 方法退出后, Runnable 任务也会继续执行。

这是输出。

Printing 0
Printing 1
Printing 2
Printing 3
Printing 4
Printing 5
Printing 6
Printing 7
Printing 8
Printing 9
Printing 10
Printing 11
Printing 12
Printing 13
Printing 14
Printing 15
Printing 16
Printing 17
Printing 18
Printing 19
Printing 20
Printing 21
Printing 22
Printing 23
Printing 24
Printing 25
Printing 26
Printing 27
Printing 28
Printing 29
Printing 30
Printing 31
Printing 32
Printing 33
Printing 34
Printing 35
Printing 36
Printing 37
Printing 38
Printing 39
Printing 40
Printing 41
Printing 42
Printing 43
Exception in thread "main" java.util.concurrent.TimeoutException
    at java.util.concurrent.FutureTask.get(FutureTask.java:205)
    at ocp.WaitTest.main(ConcurrencyTest.java:89)
Printing 44
Printing 45
Printing 46
Printing 47
Printing 48
Printing 49
Printing 50
Printing 51
Printing 52
Printing 53
Printing 54
Printing 55
Printing 56
Printing 57
Printing 58
Printing 59
Printing 60
Printing 61
Printing 62
Printing 63
Printing 64
Printing 65
Printing 66
Printing 67
Printing 68
Printing 69
Printing 70
Printing 71
Printing 72
Printing 73
Printing 74
Printing 75
Printing 76
Printing 77
Printing 78
Printing 79
Printing 80
Printing 81
Printing 82
Printing 83
Printing 84
Printing 85
Printing 86
Printing 87
Printing 88
Printing 89
Printing 90
Printing 91
Printing 92
Printing 93
Printing 94
Printing 95
Printing 96
Printing 97
Printing 98
Printing 99
Run Code Online (Sandbox Code Playgroud)

任何的想法 ?

Sla*_*law 5

有几件事正在发生。首先,所使用的线程Executors.newSingleThreadExecutor()非守护线程。正如Thread提到的文档,非守护线程将使 JVM 保持活动状态。

当 Java 虚拟机启动时,通常有一个非守护线程(通常调用main某个指定类的方法)。Java 虚拟机继续执行线程,直到发生以下任一情况:

  • exit类的方法Runtime已被调用,安全管理器已允许退出操作发生。
  • 不是守护线程的所有线程都已死亡,无论是从对run方法的调用返回,还是通过抛出传播到run方法之外的异常。

其次,ExecutorService.shutdown()不会取消任何排队或当前正在执行的任务。这只是一个信号,ExecutorService不再接受新任务并在所有现有任务完成后终止。从Javadoc

启动有序关闭,其中执行先前提交的任务,但不会接受新任务。如果已经关闭,调用没有额外的效果。

此方法不等待先前提交的任务完成执行。使用 awaitTermination 来做到这一点。

如果您想ExecutorService立即尝试并终止 ,则必须使用ExecutorService.shutdownNow().

尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务列表。

此方法不会等待主动执行的任务终止。使用 awaitTermination 来做到这一点。

除了尽力尝试停止处理正在执行的任务之外,没有任何保证。例如,典型的实现将通过 Thread.interrupt() 取消,因此任何未能响应中断的任务可能永远不会终止。

正如 Javadoc 所述,即使使用shutdownNow. 开发人员必须对任务进行编码以响应中断。

这导致了第三件事:您的任务不会对中断做出响应。当线程被中断时,WhileThread.sleep会抛出一个异常,当抛出异常时,你不会跳出循环;您的代码只是打印堆栈跟踪,然后继续进行下一次迭代。要解决此问题,请在块的末尾添加一条语句。InterruptedExceptionbreakcatch

您还可以选择使用自定义ThreadFactoryvia Executors.newSingleThreadExecutor(ThreadFactory)。如果您有工厂返回守护进程线程,那么 JVM 将在 main 返回后退出。