System.exit()将突然退出JVM,不会发生正常关闭。但System.exit()具有正常关闭的挂钩。但它陷入僵局..
class ExitPuzzle{
private static final Object lock = new Object();
public static void main(String... args) {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Locking");
synchronized (lock) {
System.out.println("Locked");
}
}
}));
synchronized (lock) {
System.out.println("Exiting");
System.exit(0);
}
}
}
输出是::
Exiting
Locking
问题是为什么在System.exit(0)时JVM没有被关闭;被执行?为什么会陷入僵局?开发人员在代码中使用ShutDowmHook时是否需要注意,还是不允许编写死锁代码?
答案 0 :(得分:2)
上面的程序死锁是因为两个线程要求相同的锁,并且持有锁的那个永远不会放手。在上面的简单示例中,根本不需要锁定。
起初可能并不清楚这里有多个线程,所以要确认这是Runtime.addShutdownHook文档中的片段
关闭钩子只是一个初始化但未启动的线程。什么时候 虚拟机开始关闭序列,它将启动所有 注册的shutdown挂钩以某种未指定的顺序挂起并让它们运行 同时进行。
另一个可能不清楚的方面是,由于死锁,对System.exit(0)的调用不会退出。这是因为System.exit(0)会阻塞,直到关闭线程全部完成。这可以通过阅读下面的代码来确认,该代码取自ApplicationShutdownHooks.runHooks,并在System.exit(0)中稍微调用一下。我用评论突出了两个关键线。 1)新线程的启动,以及2)阻塞直到它们完成。并且如上所述,此join()将不会返回,因为在返回AFTER join()之前,注册的关闭挂钩所需的锁将不会被释放。这种循环情况是死锁的定义,A在B完成之前不能继续,B在A完成之前不能继续;因此无法取得进展。
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
for (Thread hook : threads) {
hook.start(); // STARTS THE EXTRA THREADS
}
for (Thread hook : threads) {
try {
hook.join(); // WAITS FOR THE EXTRA THREADS TO FINISH
} catch (InterruptedException x) { }
}
}
答案 1 :(得分:0)
Runtime.addShutdownHook上的javadoc有很多细节:
关闭挂钩在虚拟生命周期的微妙时间运行 机器,因此应该进行防御性编码。他们应该在 特别是,写成线程安全并避免死锁 尽可能的。他们也不应盲目依赖服务 可能已经注册了自己的关机钩子,因此可能 他们自己在关闭的过程中。试图使用其他 基于线程的服务,例如AWT event-dispatch thread,for 例如,可能会导致死锁。
答案 2 :(得分:0)
Runtime.getRuntime().halt(0)
具有延迟计时器是根据this文章避免死锁的另一种选择。