1

这是我关于尝试使用中断结束/退出线程和处理 Ctrl-c 结束的第二篇文章。我不确定我是否理解它,但这是我最好的尝试。我需要更清楚的概念,请尽可能提供代码示例。

有两个类,主类Quitit和另一个类thething。主班。

通过终端加载程序时(Linux 上的情况):

Java -jar Quitit.jar

当你 Ctrl-c 关闭它时,我说你需要:

Runtime.getRuntime().addShutdownHook()

您的线程,以便它们在关闭时被杀死。

  1. 这是正确的处理方法吗?
  2. 为什么它不允许您调用一个方法以便它可以优雅地关闭?

当不通过 Ctrl-C 关闭并且您希望通过 Thread.Interrupt() 关闭时,下面的程序是否正确使用它?

  • 我是否正确地说 Thread.Join() 会暂停调用线程,直到目标线程在继续之前死掉?
  • 您将如何在实现 Runnable线程上实现/调用相同的Runtime.getRuntime().addShutdownHook()而不是 extends Thread ?

退出类:

public class Quitit extends Thread {

    public Quitit(String name) {
        try {
            connect("sdfsd");
        } catch (Exception ex) {

        }
    }

    void connect(String portName) throws Exception {
        Thread thh = new thething("blaghname");
        Runtime.getRuntime().addShutdownHook(thh);
        thh.start();
        System.out.println("Thread Thh (thething) Started()");

        while (true) {
            try {
                Thread.sleep(2000);

                if (thh.isAlive()) {
                    System.out.println("Thread Thh (thething) isAlive");
                    if (thh.isInterrupted()) {

                        System.out.println("Thread Thh (thething) is Inturrupted Should be Shutting Down");

                    } else {
                        System.out.println("Thread Thh (thething) is Not Inturrupted");
                        thh.interrupt();
                        System.out.println("Thread Thh (thething) Inturrput Sent");
                        System.out.println("Thread Thh (thething) Joined()");
                        thh.join();

                    }

                } else {
                    System.out.println("Thread Thh (thething) isDead");
                    System.out.println("Main Thread:: can now end After Sleep off 2 seconds");
                    Thread.sleep(2000);
                    System.out.println("MMain Thread:: Sleep Ended Calling Break");
                    break;
                }

            } catch (InterruptedException xa) {
                System.out.println("Main Thread:: ending due to InterruptException second Break called");
                break;
            }
        }

        System.out.println("Main Thread:: Outside While(true) via Break call");
    }

    public static void main(String[] args) {
        try {

            Thread oop = new Quitit("");
            Runtime.getRuntime().addShutdownHook(oop);
            oop.start();

        } catch (Exception ezx) {
            System.out.println("Main Thread:: Not Expected Exception");
        }
    }
}

事物类:

public class thething extends Thread {

    thething(String name) {
        super(name);
    }

    @Override
    public void run() {

        while (true) {
            try {
                System.out.println("thething class:: Inside while(true) Loop, now sleeping for 2 seconds");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                try {

                    System.out.println("thething class:: has been Inturrupted now sleeping for 2 seconds!!");
                    Thread.sleep(2000);
                    break; // Will Quit the While(true) Loop
                } catch (InterruptedException ex) {
                    System.out.println("thething class:: Second InterruptedException called !!");
                }
            }
        }

        System.out.println("thething class:: Outside while(true) and now thread is dying");
    }
}

输出::

run:
Thread Thh (thething) Started()
thething class:: Inside while(true) Loop, now sleeping for 2 seconds
Thread Thh (thething) isAlive
Thread Thh (thething) is Not Inturrupted
Thread Thh (thething) Inturrput Sent
Thread Thh (thething) Joined()
thething class:: has been Inturrupted now sleeping for 2 seconds!!
thething class:: Outside while(true) and now thread is dying
Thread Thh (thething) isDead
Main Thread:: can now end After Sleep off 2 seconds
MMain Thread:: Sleep Ended Calling Break
Main Thread:: Outside While(true) via Break call
FINISHED - BUILD SUCCESSFUL (total time: 8 seconds)
4

4 回答 4

4

我很惊讶 control-c 并没有杀死你的程序。它适用于我的测试程序,并且应该适用于大多数 Unix 变体。

当您 Ctrl-c 关闭它时,我说您需要(设置关闭挂钩)以便线程在关闭时被杀死是正确的。

不,这是正确的。当您想显式清理某些处理时,使用关闭挂钩。它们与正在运行的线程以及它们如何终止无关。

为什么它不允许您调用一个方法以便它可以优雅地关闭?

因为那不是它的工作。

这是正确的处理方法吗?

我不确定“那个”是什么。正如其他人所提到的,JVM 在最后一个非守护线程完成时完成。那时,JVM 会杀死所有守护线程并退出。如果您希望在关闭时杀死后台线程,那么您可以执行以下操作:

Thething theThing = new TheThing();
// set it to be a daemon thread before it starts
theThing.setDaemon(true);
theThing.start();

如果您询问干净地终止线程的正确方法,那么您可以使用volatile boolean或中断线程。通常这意味着启动线程的类会保留一个句柄来执行它。自从QuitIt开始TheThing上课以来,如果它使用 a ,它将执行以下操作volatile boolean

void connect(String portName) throws Exception {
    Thread thh = new TheThing("blaghname");
    thh.start();
    ...
    // we are ready to kill the thread now
    tth.shutdown = true;
}

然后在 中TheThing,该run()方法将执行以下操作:

public class TheThing extends Thread {
    volatile boolean shutdown = false;
    public void run() {
       while (!shutdown) {
          ...
          // you can also test for shutdown while processing
          if (shutdown) {
             return;
          }
       }
    }
}

当不通过 Ctrl-C 关闭并且您希望通过以下方式关闭时Thread.interrupt(),下面的程序是否正确使用它?

使用Thread.interrupt()是另一种方式,您可以向您的线程发出您希望它关闭的信号。您还可以以与上述布尔值类似的方式使用线程中断标志:

void connect(String portName) throws Exception {
    Thread thh = new TheThing("blaghname");
    thh.start();
    ...
    // we are ready to interrupt the thread now
    tth.interrupt();
}

意识到中断线程会在Thread. 他们的线程仍然需要用代码适当地处理中断。然后在 中TheThing,该run()方法将执行以下操作:

public class TheThing extends Thread {
    public void run() {
       while (!Thread.currentThread().interrupted()) {
          ...
       }
    }
}

中断也会导致wait(),notify()和其他方法抛出InterruptedException。处理这个问题的正确方法是:

try {
   Thread.sleep(1000);
} catch (InterruptedException e) {
   // catching the interrupted exception clears the interrupt flag,
   // so we need to re-enable it
   Thread.currentThread().interrupt();
   // probably you want to stop the thread if it is interrupted
   return;
}

我是否正确地说 Thread.join() 会停止调用线程,直到目标线程在继续之前死掉?

是的。调用join()(不带参数)将暂停调用线程,直到它加入的线程完成。所以通常你设置你的关闭标志或者你中断线程然后加入它:

tth.shutdown = true;
// or tth.interrupt()
tth.join();

您将如何在实现 Runnable 线程上实现/调用相同的 Runtime.getRuntime().addShutdownHook() 而不是 extends Thread ?

这个问题没有任何意义。如果您询问如果您已经实现了如何关闭线程Runnable,那么我上面提到的相同机制将起作用。


另一个影响 control-c 讨论的因素是信号处理程序。它们允许您捕获 control-c(和其他信号),以便您可以用它们做一些智能的事情。它们非常依赖于操作系统(当然),如果你捕捉到中断信号(SIGINT 通常由 control-c 发送)并且不停止 JVM,你就会遇到问题。

但无论如何,您都可以执行以下操作:

...
MyHandler handler = new MyHandler();
// catch the control-c signal, "TERM" is another common kill signal
Signal.handle(new Signal("INT"), handler);
...

private static class MyHandler implements SignalHandler {
    @Override
    public void handle(Signal arg0) {
        // interrupt your threads
        // clean up stuff
        // set shutdown flags
        // ...
    }
}

同样,我想说捕获中断信号(control-c)而不是关闭 JVM 是一种不好的做法。

于 2012-08-01T14:38:17.530 回答
2

你的问题有很多问题。如果你想在按下 Ctrl-C 时优雅地关闭线程,那么注册一个关闭钩子,并在这个关闭钩子的代码中,优雅地关闭你的线程。

可以通过扩展 Thread 或将 Runnable 传递给 Thread 构造函数来构造 Thread 实例。因此,如果您希望在 Runnable 中实现关闭挂钩的代码,只需执行以下操作:

Runnable r = new Runnable() {
    @Override
    public void run() {
        try {
            // code of the shutdown hook: ask running threads to exit gracefully
            for (Thread t : threadsToShutDown) {
                t.interrupt();
            }
            for (Thread t : threadsToShutDown) {
                t.join(); 
            }
        }
        catch (InterruptedException e) {
            // too bad
        }
    }
};
Runtime.getRuntime().addShutdownHook(new Thread(r));

请注意,如果其中一个线程没有响应中断,上述将阻止 JVM 退出。引入超时将是一个好主意。

另请注意,您不得启动您注册为关闭挂钩的线程。

于 2012-08-01T09:20:56.867 回答
2

当你 Ctrl-c 关闭它时,我说你需要:Runtime.getRuntime().addShutdownHook()你的线程以便它们在关闭时被杀死是正确的。

不。

根据文档addShutdownHook只需注册一个将在 VM 关闭(-ting)关闭时运行的线程。理论上,这将用作应用程序范围的终结器;当您的流程结束时会运行的东西,以“整理”。然而,它们存在一些问题,类似于不推荐使用终结器的原因。如果进程突然终止,它们不能保证运行,因此它们不能做任何关键的事情。它们在生命周期的一个微妙部分运行,因此它们可能无法以您期望的方式访问对象或与对象交互。他们需要快速运行,否则可能会在完成之前终止进程。

但是无论如何,这与您的想法完全不同。当 VM 关闭时,您的线程将退出,而您无需这样做。

此外,您必须提供一个未启动的线程作为关闭挂钩,所以我有点惊讶您IllegalThreadState在关闭时没有抛出异常。

当不通过 Ctrl-C 关闭并且您希望通过 Thread.Interrupt() 关闭时

这也不是退出多线程程序的“通常”方式。

您(大致)有两种选择:

  1. 将您的线程作为“守护进程”启动,然后将它们保留。当没有非守护线程运行时程序退出,所以当你的main线程终止时,即使你的“工作”线程仍然活着,你的程序也会退出。当然,当您的

  2. 向您的线程发出它们应该退出的信号stop(),例如调用设置布尔标志的方法,并让这些线程允许它们自己终止。run()线程在其方法返回时停止。通常,线程仅在处于某种循环中时才继续运行。简单地让你的线程在被告知退出时退出正在进行的循环将优雅地关闭它,因为它通常会在检查标志之前完成其当前的“工作块”。

中断线程不会做你所期望的。它只是在内部设置另一个布尔标志。许多阻塞操作(例如文件系统/网络/数据库方法)将定期检查此标志并InterruptedException在设置时抛出一个。这允许阻塞方法提前退出,但如果该方法不是“行为良好”,则不能保证。

因此,在您的示例中,它之所以有效,是因为Thread.sleep()它确实响应了中断,然后您将其视为退出信号。但是,如果您编写自己的方法来计算百万分之一的斐波那契数,例如,除非您的实现Thread.currentThread().interrupted()在每次迭代之间显式检查,否则中断线程将无济于事。

此外,中断几乎可以来自任何地方,很难赋予它们特定的“意义”。它们是杀死线程的信号吗?因为客户无聊而放弃当前方法的信号?因为新数据已经到来而从顶部开始的信号?在非平凡的程序中,它并不完全清楚。

鉴于此,更好的方法是使用布尔标志来传达原因,然后设置标志后中断。一个被中断的线程应该检查它的状态,看看刚刚发生了什么,然后弄清楚要做什么。例如,您可以编码thething.run()

private boolean keepRunning = true; 

public void run() {
    while(keepRunning) {
        try {             
            System.out.println("thething class:: Inside while(true) Loop, now sleeping for 2 seconds");
            Thread.sleep(2000); 
        } catch (InterruptedException e) {
            try {
                System.out.println("thething class:: has been Inturrupted now sleeping for 2 seconds!!");                        
                Thread.sleep(2000); 
            } catch (InterruptedException ex) 
            { 
                 System.out.println("thething class:: Second InterruptedException called !!");  
            }
        } 
    }
}

在 中QuitIt,在中断它之前设置thh.keepRunning = false(或者更好的是,定义一个方法,例如thh.allWorkDone()设置标志,然后调用)。

请注意,没有办法强制停止另一个线程 -有充分的理由- 您需要发出信号让线程停止,并确保线程中运行的任何内容都观察并尊重该信号。

于 2012-08-01T09:25:51.120 回答
0

最好的优势 - 使用没有显式终止的守护线程。接下来,重新设计应用程序,以便我可以使用没有显式终止的守护线程。绝对最后的手段,当没有其他方法是远程可行时,任何类型的显式关闭代码都已在此处发布。

于 2012-08-01T10:33:25.913 回答