我刚刚遇到一个有趣的问题。在Java中,如果一个线程调用System.exit()
,那么它就不能通过Thread.join()
加入。
这导致了我的问题,因为我想在我的应用程序之后使用关闭挂钩来清理,例如:
Runtime.getRuntime.addShutdownHook(new Thread() {
public void run() {
reader.join();
writer.join();
in.close();
out.close();
}
});
其思想是,它确保线程在关闭这些资源之前已经完成了各自的资源。问题是有三种情况可以调用关闭挂钩。它们是:
- 用户点击[ctrl]+[C]
- 读取器线程完成,并调用
System.exit()
- 编写器线程完成,并调用
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。