Java关闭挂钩多线程

8

我正在尝试在我的Ubuntu服务器上让一个关闭挂钩生效,但是似乎存在多个线程的问题。使用基本的ShutdownHook时,当我使用“kill <PID>”杀死进程时,下面的代码确实可以工作,这意味着关闭行为被激活。

public static void main(String[] args) {
    ShutdownHook shutDown = new ShutdownHook();
    shutDown.attachShutDownHook();

    while(true){}
}

然而,带有额外线程的同样代码却无法运行。

public static void main(String[] args) {
    ShutdownHook shutDown = new ShutdownHook();
    shutDown.attachShutDownHook();

    (new Thread() {
        public void run() {
            while ( true ) {}
        }
    }).start();

    while(true){}
}

有什么想法吗?
class ShutdownHook {

    ShutdownHook() {
    }

    public void attachShutDownHook() {

            Runtime.getRuntime().addShutdownHook(new Thread() {
                    @Override
                    public void run() {
                            System.out.println("Shut down hook activating");
                    }
            });
            System.out.println("Shut Down Hook Attached.");

    }
}

尝试在调用kill后运行jstack <PID>以查看仍在运行的线程。 - SimonC
一旦我杀死了进程,就无法运行jstack,它只会显示“没有这样的进程”。 - Reese
问题是:进程被杀死,但没有调用关闭挂钩,请发布关闭挂钩的代码,并在其第一行添加System.err.println()。 - Aubin
1
你必须还有其他没有包含在这里的东西。我刚刚尝试了你的示例(加上额外的线程),一切都按预期工作:` $ java ShutdownHook Shut Down Hook Attached. Shut down hook activating $ java ShutdownHook & [1] 4879 $ Shut Down Hook Attached.$ kill 4879 Shut down hook activating $ ` - SimonC
我在我的更复杂的应用程序中看到了这种行为,但是在您的简单示例中无法重现它。但在我的情况下,只有当我真正关闭电源(即按下电源按钮而不是发送信号)时才会发生。此外,这种方式很奇怪,因为关机不等待,就像/etc/init.d/sendsigs所说的那样,它应该等待挂起的进程(最多10秒),但是关机钩子从未运行(因为它正在记录到文件)。我实际上怀疑JVM上的信号处理错误或某些东西在SIGTERM之前使用SIGKILL杀死java。这个错误在Windows中不存在。 - i30817
显示剩余2条评论
3个回答

3

只要有非守护线程在运行,JVM 就不会退出。可以尝试在新线程上调用 setDaemon(true) 方法。


1
从我自己的一个项目中举个例子;我的核心类会产生一堆工作线程,这些工作线程运行必须完成的事务。在核心类中,我有:
Runtime.getRuntime().addShutdownHook(new ShutdownCleanup());

看起来像这样:

public final class ShutdownCleanup extends Thread() {
    public void run() {
        log("waiting for worker threads to finish...");
        while(WorkerThread.transactionInProgress()) {
            Thread.sleep(1000);
        }
        //close various sockets and resources
    }
}

当我在终端中按下Ctrl+C时,log消息会出现。因此,关闭挂钩立即激活,它不是在程序准备死亡时运行的东西,而是在接收到关闭或杀死请求时立即运行。
因此,我认为其他人所说的关机挂钩在仍有线程存活时从未被调用是错误的。如果一切都已经死了,那么关机挂钩只会激活一次,这有什么意义呢?
可能是您正在使用硬关闭,例如-9。您尝试过使用Ctrl+CSIGINT进行正常关闭吗?

是的,我已经尝试过使用没有参数的 kill <PID>CTRL+C。它们都在第一个单线程示例中起作用,但是一旦我有多个线程,它们都不起作用。也许我在设置线程时漏掉了什么? - Reese
你尝试过使用上面的 Runtime 方法吗?我不知道它会有什么区别,但也许你正在将钩子附加到一个线程而不是 JVM 上...如果可能的话。 - lynks
我正在使用Runtime。我更新了我的原始问题以显示ShutdownHook本身。 - Reese
那我真的不知道了。我的工作示例似乎与你的相同。我有16个工作线程在运行,它们实现了Runnable而不是扩展Thread,但这应该没有任何区别。你的shutdown hook方法中第一行是否有System.out.println或类似的输出语句?它必须在运行! - lynks

0

java.lang.Runtime.addShutdownHook( Thread )注册一个新的虚拟机关闭挂钩:

Java虚拟机响应两种类型的事件而关闭:

  • 程序正常退出,即最后一个非守护线程退出或调用exit(等效于System.exit)方法时;或者

  • 响应用户中断(例如键入^C)或系统范围事件(例如用户注销或系统关机)而终止虚拟机。

您的代码运行时间很长,因此关闭挂钩将永远不会被调用。

[编辑后]

你用哪个kill

kill -9是无法被捕获的

kill -2可以用来模拟ctrl-c(SIGINT)


我正在使用kill <PID>而没有额外的参数。它应该是可捕获的,并且实际上在单线程情况下确实有效。 - Reese

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接