首页 > 解决方案 > 即使在 main 函数退出后,这个 java 程序如何继续运行?

问题描述

我正在尝试学习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();
            }
        }
    }
}

提供给 ExecutorService 的 Runnable 任务需要 10 秒才能完成。我设置了 5 秒的超时时间以从未来对象中获取结果。所以很明显主方法在 5 秒后退出,因为 TimeoutException 被抛出。但是即使在 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

任何想法 ?

标签: javaconcurrency

解决方案


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

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

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

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

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

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

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

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

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

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

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

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

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


推荐阅读