Java线程和关闭挂钩



我刚刚遇到一个有趣的问题。在Java中,如果一个线程调用System.exit(),那么它就不能通过Thread.join()加入。

这导致了我的问题,因为我想在我的应用程序之后使用关闭挂钩来清理,例如:

Runtime.getRuntime.addShutdownHook(new Thread() {
    public void run() {
        reader.join();
        writer.join();
        in.close();
        out.close();
    }
});

其思想是,它确保线程在关闭这些资源之前已经完成了各自的资源。问题是有三种情况可以调用关闭挂钩。它们是:

  1. 用户点击[ctrl]+[C]
  2. 读取器线程完成,并调用System.exit()
  3. 编写器线程完成,并调用System.exit()

第一种情况,用户点击[ctrl]+[C],效果很好。但在其他两种情况中的任何一种情况下,关闭挂钩都会永远阻塞。这是一个针对已经调用System.exit()的线程调用的Thread.join()永远阻塞的连锁反应。

因此,我有两个问题。首先,我知道我可以使用Thread.join(long millis),这样它们就不会无限期地阻塞,但有人能想出一个更优雅的解决方案吗?其次,虽然可以对同一个线程调用Thread.join()两次,第二次它只会立即返回,但有人知道为什么对已经无限期调用System.exit()块而不立即返回的线程调用Thread.join()的原因吗?

System.exit如果成功,即使抛出异常也不会返回,因此线程永远不会完成。在关机挂钩之前,这不是一个问题。

变通办法是使用通常的标准锁(甚至只是用new Thread(new Runnable() { public void run() { System.exit(0); }}).start();破解它)。

您正在做的事情是特别禁止的:

停机挂钩也应迅速完成其工作。

连接随机线程不能被描述为"快速"。

显然,这些线程在退出时应该关闭其自己的流。如果他们不退出,调用join()将如何完成任务?

不建议使用System.exit(),因为它只是终止JVM。还有其他并发工具,比如CountDownLatch。您甚至可以在关机时使用finally来正确清理。

使用执行器,不要使用联接或手动创建线程。

当在java.util.concurrent中使用executor和其他东西时,您可以简单地调用shutdown并等待它终止、指定超时等。如果您使用的是java1.7,那么也可以使用try-with-resources来关闭流。这样,除了关闭遗嘱执行人之外,你就没有太多的清理工作要做了。如果没有,请使用finally块。永远不要在finally块之外的任何其他地方调用close。

最新更新