如何在不使用stop()的情况下终止一个线程?

27
Thread currentThread=Thread.currentThread();
        public void run()
        {               

             while(!shutdown)
            {                               
                try
                {
                    System.out.println(currentThread.isAlive());
                Thread.interrupted();

                System.out.println(currentThread.isAlive());
                if(currentThread.isAlive()==false)
                {
                    shutdown=true;
                }
                }
                catch(Exception e)
                {
                    currentThread.interrupt();
                }                   
            }
        }

    });
    thread.start();

你能检查一下上面的代码并帮我终止线程吗? - greeshma
2
请查看此帖子:http://forward.com.au/javaProgramming/HowToStopAThread.html。 - Bala R
看一下最佳实践:如何终止JavaME 1.2线程?,或许会有所帮助。 - Piccolomomo
3
这段代码可以编译吗? - mre
根据定义,Thread.currentThread().isAlive()永远不可能为假。这段代码毫无意义。 - user207421
3个回答

62

除了调用stop之外,另一种方法是使用interrupt来向线程发出信号,告诉它完成正在进行的任务。(这假设您希望停止的线程表现良好,如果它忽略InterruptedException并立即吞掉它们,或者不检查中断状态,则需要重新使用stop()。)

下面是我在一个线程问题答案 这里 写的一些代码,它展示了线程中断的工作方式:

public class HelloWorld {

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {

            public void run() {
                try {
                    while (!Thread.currentThread().isInterrupted()) {
                        Thread.sleep(5000);
                        System.out.println("Hello World!");
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        thread.start();
        System.out.println("press enter to quit");
        System.in.read();
        thread.interrupt();
    }
}

需要注意的一些事情:

  • 中断会导致 sleep()wait() 立即抛出异常,否则线程将一直阻塞等待指定时间。

  • 请注意,不需要单独的布尔标志。

  • 被停止的线程通过检查中断状态并在 while 循环外捕获 InterruptedExceptions 来配合协作(使用它来退出循环)。中断是一个可以使用异常进行流程控制的地方,这就是它的全部意义。

  • 在 catch 块中设置当前线程的中断标志在技术上是最佳实践,但对于这个示例来说有点过度设计,因为没有其他东西需要中断标志设置。

对发布的代码的一些观察:

  • 发布的示例不完整,但是将当前线程的引用放在一个实例变量中似乎是一个坏主意。它将被初始化为创建对象的任何线程,而不是执行 run 方法的线程。如果同一个 Runnable 实例在多个线程上执行,则实例变量大部分时间都不会反映正确的线程。

  • 检查线程是否存活的操作必定始终返回 true (除非当前线程实例变量引用错误的线程),Thread#isAlive 仅在线程执行结束后为 false,它不会因为被中断而返回 false。

  • 调用 Thread#interrupted 将清除中断标志,并且在这里没有意义,特别是由于返回值被丢弃。调用 Thread#interrupted 的目的是测试中断标志的状态然后清除它,它是被抛出 InterruptedException 的东西使用的便捷方法。


1
首先,这段代码假定将使用sleep或其他可中断的阻塞调用。这并非必须如此。该代码可以用于执行一些复杂的计算。使用线程中断需要更加小心。如果存在多个IO,则某些IO也可能会被中断,我们可能需要仔细清理对象等。我建议通常使用标志变量作为更安全的选项。 - Raze
1
@Raze:一定要小心谨慎。这就是为什么我在示例中留下了Thread.currentThread().interrupt(),以演示确保中断标志被维护。无论如何,对于OP的目的,我认为这个标志并不是必需的,尽管它不会有任何损害,而且你的示例实现得很好。 - Nathan Hughes
@NathanHughes 我看到了你的回答,非常有帮助,只是我不明白在 catch 中为什么要再次调用 interrupt?为什么同一个线程会再次调用 interrupt? - Abidi
@Abidi:在这种情况下并不重要。如果您有嵌套组件,所有组件都必须响应中断,那么您需要这样做以确保每个人都看到中断。 - Nathan Hughes
这是一个非常好的答案!我一直都是利用单独的标志位实现,但这个方法更有意义。至于正确关闭资源的问题,它并不是线程问题,而是错误处理问题。IO应该在finally块中关闭而不是try或catch块中。这经常出错,jdk7发布了try-with-resources语句来解决这个问题。 - John Mercier
显示剩余5条评论

14

通常情况下,当线程被中断时,它会被终止。那么为什么不使用本地布尔值呢?试试isInterrupted()

   Thread t = new Thread(new Runnable(){
        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                // do stuff         
            }   
        }});
    t.start();

    // Sleep a second, and then interrupt
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {}
    t.interrupt();

2
+1,人们喜欢自己编写(布尔)代码,但您必须使其易失性并进行诸如isInterrupted之类的标准中断策略,这样更加优雅:D - Toby
如果我在不调用Thread.sleep(1000)的情况下调用thread.interrupt();会发生什么,这样做安全吗? - Lovekush Vishwakarma
@LovekushVishwakarma https://docs.oracle.com/javase/10/docs/api/java/lang/Thread.html#interrupt() - mre

6
使用布尔标志来通知线程是一种不错的方法。
class MyRunnable implements Runnable {

    public volatile boolean stopThread = false;

    public void run() {
            while(!stopThread) {
                    // Thread code here
            }
    }

}

创建一个名为myrunnable的MyRunnable实例,将其包装在新的Thread实例中并启动该实例。当您想要标记线程停止时,请设置myrunnable.stopThread = true。这样,它不会在执行到一半时被停止,只会在我们期望它停止的地方停止。

如果将标志与阻塞队列结合使用,则可能会很危险。 - Luke
@Luke,你是在说线程因为等待队列而无法退出的情况吗? - Raze
基本上,如果您在 while 循环中有一个阻塞操作,例如 queue.put,并且您的队列已满并且队列的所有消费者都已完成,则即使您设置了停止标志,您的任务也无法完成。 - Luke
@luke,对于使用Object.wait或许多NIO调用的任何操作都是如此,这种情况下可以根据需要添加标志,也可以不添加,因此Nathan的答案是正确的。但是,在您没有权限中断类(很少见,但我曾经遇到过)以及线程尚未启动的边缘情况下,这可能无法正常工作。 - Raze
@Luke,你能告诉我如何在你所描述的情况下停止线程吗?我的意思是当线程在队列上阻塞并应该被停止时。 - user3740179
在这种情况下,您可以使用线程的中断方法(请参见Nathan的答案)。如果有多个调用将执行等待操作,则应在每个此类调用之后检查isInterrupted。 - Raze

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