【问题标题】:How this java program keeps running even after main function exits?即使在 main 函数退出后,这个 java 程序如何继续运行?
【发布时间】:2019-05-15 15:09:43
【问题描述】:

我正在尝试学习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

有什么想法吗?

【问题讨论】:

  • ExecutorService.shutdown() 不会取消当前正在执行的任务。
  • shutdown() 方法将允许先前提交的任务在终止之前执行,而 shutdownNow() 方法阻止等待的任务启动并尝试停止当前正在执行的任务。来自 API 文档:docs.oracle.com/javase/7/docs/api/java/util/concurrent/…
  • 也许你想调用ExecutorService.shutdownNow(),它试图停止所有当前正在运行的Runnables。你可以看看JavaDocs。请注意不保证

标签: java concurrency


【解决方案1】:

发生了一些事情。首先Executors.newSingleThreadExecutor()使用的线程是non-daemon线程。正如Thread 的文档所述,非守护线程将使JVM 保持活动状态。

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

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

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

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

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

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

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

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

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

正如 Javadoc 所述,即使使用shutdownNow,也无法保证执行任务会终止。开发人员必须对任务进行编码以响应中断。

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

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

【讨论】:

    【解决方案2】:

    TimeoutException 发生在运行main() 函数的线程中,而不是执行器服务正在执行的线程中。

    因此,虽然运行 main 函数的线程已经执行完毕,但 JVM 仍然会等待所有其他正在执行的线程(在这种情况下是打印数字的线程)终止。

    【讨论】:

      【解决方案3】:

      shutdownNow() 会尝试中断线程,但您在睡眠中捕获中断异常。所以你应该把你的整个代码放在 try catch 块中。

      这可能对你有帮助

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-03-03
        • 2021-11-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多