【问题标题】:Threads Java Inturrupts线程 Java 中断
【发布时间】:2012-08-01 08:36:06
【问题描述】:

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

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

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

Java -jar Quitit.jar

当你 Ctrl-c 关闭它时,我说你需要这样做是正确的:

Runtime.getRuntime().addShutdownHook()

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

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

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

  • 我是否正确地说 Thread.Join() 会暂停调用线程,直到目标线程在继续之前停止?
  • 您将如何在 implements 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");
        }
    }
}

TheThing 类:

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)

【问题讨论】:

  • 你需要在Quitit构造函数中调用super()
  • 好吧,这又让人困惑了,所以 interrupt() 不是杀死线程的正确方法。您应该使用一个布尔标志来终止线程,该标志将控制 while(true)/while(false) 循环并让线程耗尽。仅当您具有诸如串行端口读取()(IO读取)之类的保持状态时才应使用中断,在这种情况下,您可以在没有超时的情况下进行读取(),从而可以无限期地保持程序?似乎真的缺少清晰度还是只有我?
  • @DevilCode:中断是请求线程停止的正确方法。不需要任何额外的标志:中断状态可以做到这一点。
  • JB Nizet:这与其他 cmets 背道而驰。
  • 是的。我在对 Andrzej 的回答的评论中解释了我的立场

标签: java multithreading runtime


【解决方案1】:

我很惊讶 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 类,如果它使用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()?

这个问题没有任何意义。如果你问的是如果你已经实现了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 是一种不好的做法。

【讨论】:

    【解决方案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 退出。引入超时是个好主意。

    还要注意,你不能启动你注册为关闭钩子的线程。

    【讨论】:

      【解决方案3】:

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

      没有。

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

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

      此外,您必须提供一个 unstarted 线程作为关闭挂钩,所以我有点惊讶您在关闭时没有收到 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(),然后调用that)。

      请注意,没有办法强制停止另一个线程 - with good reason - 您需要发出信号 让线程停止,并确保线程中运行的任何东西都遵守并尊重该信号。

      【讨论】:

      • 我实现了程序的修改版本以使用 Thread.currentThread().interrupted() 但即使我确实发出了中断,它也会返回为假。所以已经不可靠只是不知道为什么它在被打断时不显示。
      • @DevilCode 如果你所有的线程都是守护线程,是的——尽管main 线程永远不是守护线程。守护进程非常适合那些倾向于永远运行的线程,尤其是当它们正在执行“支持/后台”任务时。您的理解在很大程度上也是正确的 - 尽管关闭挂钩对于 Ctrl-C “工作”来说不是必要,但如果您想优雅地退出你的线程。如果你不注册一个关闭钩子,你所有的线程都会简单地停止,这可能很好。
      • 至于roseindia 链接——我认为我的链接是两全其美的。 :) 中断的问题是(如上所述)它们确实总是意味着您应该退出当前线程。这篇文章对布尔值的唯一抱怨是它可能需要一些时间才能看到标志。但是通过设置标志并立即中断线程,线程将立即唤醒并检查其标志 - 避免单独使用任何一种方法的负面影响。
      • 我不同意关于中断的部分。要知道如何停止线程,您需要知道它如何在内部工作以设置正确的标志。所以,既然你需要线程的协作才能停止它,为什么要使用两个不同的标志而不仅仅是中断状态呢?无论如何总是需要中断状态,因为它是阻塞方法检查以了解它们是否必须突然退出的唯一标志。
      • 停止线程真的很容易——只要你是一个操作系统就可以终止一个完整的进程。如果可能的话,应该避免其他任何事情——用户空间代码没有必要的工具来安全地终止所有可能内核上所有可能状态下的线程。
      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2014-08-16
        • 1970-01-01
        • 1970-01-01
        • 2012-10-15
        • 2016-03-11
        • 1970-01-01
        • 2020-07-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多