Java关机钩子未运行

4

我对Java /线程不熟悉,并继承了以下代码。这是一个命令行程序,main()仅启动5-6种不同类型的线程,并使用^C退出。我想添加一个关闭钩子来正确关闭所有线程,并将其修改为以下方式。

我在所有线程中都添加了Shutdown钩子和stopThread()方法(例如MyWorker类中的方法)

问题是当我按下^C时,我看不到线程run方法的结束消息。这是在后台完成还是我的方法有问题?此外,是否有更好的模式我应该遵循?

谢谢

 public class Main {
     public static MyWorker worker1 = new MyWorker();
     // .. various other threads here

     public static void startThreads() {
         worker1.start();
         // .. start other threads
     }

     public static void stopThreads() {
         worker1.stopThread();
         // .. stop other threads
     }

     public static void main(String[] args)
             throws Exception {

         startThreads();

         // TODO this needs more work (later)

         Runtime.getRuntime().addShutdownHook(new Thread() {
             @Override
             public void run() {
                 try {
                     stopThreads();
                 } catch (Exception exp) {

                 }
             }
         });
     } }

 public class MyWorker extends Thread {
     private volatile boolean stop = false;

     public void stopThread() {
         stop = true;
     }

     public void run() {
         while (!stop) {
             // Do stuff here
         }
         // Print exit message with logger
     } 
}

3
你应该将 stop 声明为 volatile - Tudor
好观点(问题也已经编辑过了);) 但是并没有解决问题。 - jimkont
5个回答

11

有时,关闭挂钩不会被执行!

首先要记住的是,不能保证关闭挂钩总是能运行。如果 JVM 由于某些内部错误而崩溃,则可能在没有机会执行任何指令的情况下崩溃。

此外,如果操作系统发出 SIGKILL (http://en.wikipedia.org/wiki/SIGKILL) 信号 (kill -9 在 Unix/Linux 中) 或 TerminateProcess (Windows),则应用程序需要立即终止,甚至不等待任何清理活动。除上述原因外,还可以通过调用 Runime.halt() 方法在不允许关闭挂钩运行的情况下终止 JVM。

关闭挂钩在应用程序正常终止时被调用(当所有线程完成或者调用 System.exit(0) 时)。此外,在 JVM 由于外部原因而关闭时,例如用户请求终止(Ctrl+C),操作系统发出 SIGTERM(普通 kill 命令,没有 -9)或者操作系统正在关闭时也会触发关闭挂钩。


8
当您调用System.exit()或通过信号终止时,它会停止所有现有线程并启动所有关闭挂钩。也就是说,在挂钩开始之前,您的所有线程都可能已经死亡。
与其试图清理停止线程,您应该确保资源被干净地关闭。

2
我该如何处理线程终止呢?也就是说,如何在线程本身中调用一些清理代码? - jimkont

5

我猜你可以把你的代码转移到ExecutorService

private final ExecutorService pool;
pool = Executors.newFixedThreadPool(poolSize);
pool.execute(Instance of Runnable);
pool.shutdown(); 

ExecutorService.shutdown

该方法会启动一个有序的关闭过程,在这个过程中之前提交的任务会得到执行,但是不会接受新任务。如果已经关闭,则调用本方法不会产生任何额外的效果。


我想知道如果在命令行上按Ctrl-C停止程序,这个程序是否能正常工作?pool.shutdown()会被调用吗?我相信在jvm关闭钩子中... - RuntimeException
是的,awaitTermination也可以从shutdown hook中使用,但尽管它保证大多数情况下会被执行,但它永远不能保证总是会被执行。 - Amit Deshpande
在我的情况下,我猜这行不通。我有5-6种不同类型的线程:3种生产者、1个消费者和几个其他辅助线程。 - jimkont
它可能因为其他原因而无法工作。它应该能够与不同类型的线程一起工作。Java线程实现了Runnable接口。 - RuntimeException
它可能因为其他原因而无法工作。它应该能够与不同类型的线程一起工作。Java线程实现了Runnable接口。除了Ctrl C之外,您是否有任何其他机制来关闭?也许可以提示输入命令?输入“exit”以停止程序。然后调用pool.shutdown()。 - RuntimeException

2
尝试将线程设置为守护线程。
添加一个构造函数。
public MyWorker(boolean isDaemon) {
this.setDaemon(true);
}

在调用start之前,可以将其设置为守护进程。

worker1.setDaemon(true);
worker1.start();

当你按下Ctrl C并退出时,线程将停止。

0
这里发生的情况是你调用了 stopThread() 方法,但在终止之前你并没有等待线程实际完成。
如果在停止 JVM 之前对所有线程调用 join(),你可能会看到你的“停止日志”。
public static void stopThreads() {
     worker1.stopThread();
     // .. stop other threads

    for(Thread t: workers) {
       t.join();
    }
 }

我已经尝试过这个但是没有成功。我甚至在startThread() / Shutdown hook之后的main()函数中尝试将它们连接起来。 - jimkont

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