如何停止一个无用地无限运行的线程

24
在下面的代码中,我有一个while(true)循环。假设在try块中有一些代码,线程应该执行大约需要一分钟的任务,但是由于某些预期的问题,它永远运行下去了。我们能够停止那个线程吗?
public class thread1 implements Runnable {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        thread1 t1 = new thread1();
        t1.run();

    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            try{        
                Thread.sleep(10);

            }
            catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}

6
在Windows系统中,打开任务管理器,结束javaw进程即可。 :) - Buhake Sindi
@The Elite Gentleman 你在说什么?这只适用于Windows用户,这不是一种跨平台的方式来做,因此,它并不是很Java :oP - SteeveDroz
8
不是直接回答你的问题,但不要犯调用Runnable对象的run()方法而不是Thread对象的start()方法的错误。这是一个很常见的错误 :S。 - m2o
@Oltarus,对于非Windows系统,找到一个Java运行时进程ID,并使用kill -9 pid,其中pid是Java运行时进程的ID。开心吗?:P - Buhake Sindi
@优秀的绅士,非常高兴! - SteeveDroz
@The Elite Gentleman,Windows 命令不像 kill -9 那样出名,但确实存在:taskkill /pid 或在您的情况下 taskkill /IM javaw.exe - bestsss
6个回答

49

首先,您没有在此处启动任何线程! 您应该创建一个新线程并将您的命名混乱的thread1Runnable传递给它:

thread1 t1 = new thread1();
final Thread thread = new Thread(t1);
thread.start();

现在,当你真正拥有一个线程时,有一个内置的功能来中断正在运行的线程,称为... interrupt()

thread.interrupt();

然而,仅设置此标志是没有作用的,您必须在运行的线程中处理它:
while(!Thread.currentThread().isInterrupted()){
    try{        
        Thread.sleep(10);
    }
    catch(InterruptedException e){
        Thread.currentThread().interrupt();
        break; //optional, since the while loop conditional should detect the interrupted state
    }
    catch(Exception e){
        e.printStackTrace();
    }

需要注意两点: 当线程isInterrupted()时,while循环现在将会结束。但是,如果在线程休眠期间被中断,JVM会友善地通过抛出InterruptedExceptionsleep()中通知您。捕获它并终止您的循环。就这样!


至于其他建议:

已过时。此方法天生不安全[...]

  • 添加自己的标志并注意它没问题(只需记住使用AtomicBooleanvolatile!),但是如果JDK已为您提供了类似的内置标志,为什么要麻烦呢?增加的好处是可以中断sleep,使线程中断更具响应性。

最有可能你想要检查它 Thread.interrupted() (该方法会清除标志) - bestsss
+1,但我添加了重新设置中断标志。通常最好遵循最佳实践。清除中断标志(如@bestsss建议的那样)通常不是一个好主意,因为使用上下文未知。如果OP正在尝试编写某个工具或一些库/客户端代码呢?您不想使用一些调用API的方式,这些API会从主程序中恶意隐藏中断请求,对吧? - Tim Bender
@Tim,代码没有设置标志,所以应该使用Thread.interrupt(),但最好的做法是抛出异常。虽然保留标志有所帮助,但最好抛出异常,这样代码流会快速转移到作用域之外,而不是在每个语句中轮询标志。我不认为不断轮询标志是最佳选择。库代码通常会这样做,即Thread.interrupted() + throw InterruptedException,你可以在java.util.concurrent.locks中看到它。 - bestsss
@bestsss ... 对,它会抛出一个InterruptedException,这样你就不必在每个阻塞操作的返回处检查标志。然而,在处理InterruptedException时,API应该做的正确的事情要么是重新抛出它,要么重新设置中断标志。你可以阅读IBM的白皮书http://www.ibm.com/developerworks/java/library/j-jtp05236/index.html或者阅读Brian Goetz的《Java并发实战》以获取更多细节。附注:Thread上的interrupt()方法不是静态的。 - Tim Bender

5

停止线程的正确方法是通过interruptstop()已被弃用且可能会产生不良副作用):

t1.interrupt()

这将导致像Thread.sleep()Object.wait()这样的方法抛出InterruptedException异常。

然后,只需为此异常添加一个catch块,并简单地从while循环中break出来即可。

编辑:我现在意识到你的无限循环正在主线程中运行,没有由你的代码创建的线程,它只是在运行一个Runnable。你需要在某个时候调用Thread.start()来生成一个新线程。


4

将捕获中断移到循环外。这不需要更多的代码行,它只是正确处理中断,即动作被中断。

public void run() {
    try{        
        while(true) {
            Thread.sleep(10);
        }
    } catch(InterruptedException e){
        System.out.println("Thread interrupted"));
    }
}

2
创建一个名为boolean keepGoing的字段,在启动线程之前将其设置为true,然后用while (keepGoing)替换while (true)。在某个时候,你可以决定何时将keepGoing的值更改为false,这样它就会退出循环。请保留HTML标签。

@Tomasz Nurkiewicz 已修复,已将其替换为 keepGoing - SteeveDroz
1
-1,因为这本质上重复了中断标志的功能,但以一种非常非标准的方式实现,没有考虑线程可见性。 - Tim Bender

2

唯一停止任意线程的方法是通过中断它。保留对它的引用,然后调用interrupt方法。


1

我建议使用Thread.interrupt()(如@Bohemian所提到的)。 它比使用临时标志有几个优点:

  • 您不需要创建和使用特定于应用程序的API来执行此操作。 (并且中断保证线程安全...)

  • Thread.interrupt()将中断在wait()join中被阻塞的线程,或者可能1一些阻塞I / O调用。

但是,它不是万能药。 如果您要停止的线程正在执行常规代码,则需要定期检查其interrupted()标志,否则它将无法停止。 这使我们处于与临时标志机制相同的船上。 线程必须合作,否则它不能(安全地)停止。

1- 这是一个模糊的领域。一方面,有一个InterruptedIOException,其Javadoc中写道:“信号表明I/O操作已被中断”。另一方面,在各种java.io流类的Javadoc中没有明确提到该异常。


确实有些第三方代码可能无法正确处理interrupted标志,因此中断可能会被“吃掉”。但是如果您有源代码,可以检查这一点。而且情况与第三方代码不关注您的特别制定的标志机制并没有太大区别。


不建议使用Thread.stop()。它基本上是不可靠的。有些人声称它对他们有效,但在我看来,他们要么处理的是一个特殊情况,要么就是运气好。


@Stephen,很多代码(称之为第三方)倾向于吃掉标志(或不重置它)。顺便说一下,线程暂停/停止并检查堆栈(爬行它)以查找危险的停止位置对我来说似乎是个好办法。 - bestsss
@Stephen,真正脆弱的代码是这种lock.lock(); try{}finally( lock.unlock()} 会在finally块中抛出ThreadDeath异常,这完全爆炸了。java.util.concurrent.locks.中的任何堆栈位置都是真正糟糕的,另一方面,大多数的java.net可以很好地停止。我从不建议任何人尝试自己编写此类代码,因为它(我想)还依赖于JVM等。Java没有C#线程停止语义,这有些遗憾。 - bestsss
@Stephen,我不是在说web服务器。泄漏对我来说是不可接受的。如果你没有泄漏,就没有必要重新启动。想象一下,如果你有一个多核心的MMO游戏服务器,带有共享内存;你会想要重新启动它吗?虽然我的情况不完全相同,但也并不那么不同。 - bestsss
@bestsss - 没有质量上的区别。如果您有一个需要终止线程的应用程序,则说明您拥有一个设计不良和/或存在漏洞的应用程序。如果您的应用程序没有故障转移和/或偶尔重启的预留,则说明您拥有一个设计不良的应用程序。对于每个组件实例都需要持续运行的系统是设计不良的。最好的解决方案是修复设计问题...而不是采取“英雄式”措施来避免重新启动。 - Stephen C
@Stephen,由于某些意外的错误/情况,线程“需要被杀死”,这不是正常情况,但是它确实会发生。能够修复错误而不会丢失几千个套接字(及其关联的客户端状态)对我来说是一个壮举。重新部署一些模块只需要不到一秒钟的时间。我以游戏为例,无论是否有其他服务器承担负载,都不可能在不对玩家造成重大不良影响的情况下关闭整个服务器。我从未告诉客户端如果服务器崩溃就不能恢复,但这并不是为客户端提供良好体验的计划。 - bestsss
显示剩余4条评论

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