如何在Java中终止一个线程?

417

如何在Java中终止一个java.lang.Thread


2
到目前为止,您无法终止一个线程;因为destroy()从未实现,因为它容易导致死锁。 - AZ_
1
我更喜欢这个问题的ExecutorStatus答案:https://dev59.com/IHE95IYBdhLWcg3wheRR - Kirby
9
“我认为Java应该实现一个安全的停止/销毁方法来处理那些你无法控制的失控线程,尽管存在失去锁等问题。” 那么你希望有一个“不安全”的线程停止方法。我认为你已经有了一个这样的方法。 - DJClayworth
4
很惊奇2009年会有哪些问题能够获得212个赞。今天这些问题可能会被直接删除。 - Jonathon Reinhart
8
为什么呢?即使在现今,这似乎是一个合理的问题。也许你不知道当你有无法控制的线程时,只能使用已弃用的函数来处理会引起多大的挫败感呢? - TFuto
显示剩余2条评论
17个回答

205

请看Sun关于为什么废弃Thread.stop()线程讨论。它详细说明了为什么这是一种不好的方法以及通常应该采取什么措施来安全地停止线程。

他们推荐的方法是使用共享变量作为标志,要求后台线程停止。然后可以由请求线程终止的不同对象设置此变量。


3
如果你在使用电脑的话,那就没问题了。但是如果你在开发移动设备上的软件(比如安卓系统),那么你可能会遇到OutOfMemoryError的问题。 - AZ_
2
需要注意的是,为确保通过标志及时通信停止请求,变量必须是易失性的(或者访问变量必须是同步的),正如建议中所述。 - mtsz
2
该链接目前已失效。不过,我在archive.org上找到了它:http://web.archive.org/web/20090202093154/http://java.sun.com/j2se/1.4.2/docs/guide/misc/threadPrimitiveDeprecation.html - Jay Taylor
1
如果线程正在等待某些响应呢?例如用户输入或套接字接收数据? - Shashwat
2
我使用java.sql.DriverManager中的方法getConnection()。如果连接尝试时间过长,我会尝试通过调用Thread.interrupt()来终止相应的线程,但它并不影响线程。然而,Thread.stop()可以工作,尽管Oracle表示如果interrupt()无法工作,则不应该使用它。我想知道如何使其工作并避免使用已弃用的方法。 - Danny Lo
显示剩余5条评论

132

@Fredrik 当调用interrupt()方法时,线程上下文会发生什么?主要问题与每个新线程的日志生成有关。 - ABcDexter
3
@ABcDexter,整个意思在于中断不会打断任何东西,它仅向线程内的代码(或被线程调用的代码)发信号,告诉它有人要求它中断正在进行的任何操作。然后,该线程应该优雅地停止处理并返回,就像完成了它应该做的事情一样(此时,线程上下文可能也被丢弃)。另一方面,如果您真的强制停止了线程,那么您的问题将非常好,答案是未定义的。 - Fredrik

67

在Java中,线程并不会被强制结束,而是以一种合作的方式进行停止。线程被请求终止后,可以优雅地关闭。

通常使用一个volatile boolean字段,线程定期检查该字段,并在其设置为相应值时终止。

我不建议使用boolean来检查线程是否需要终止。如果您使用volatile作为字段修饰符,这将可靠地工作,但如果您的代码变得更加复杂,例如在while循环内使用其他阻塞方法,可能会发生您的代码根本无法终止,或者需要更长时间才能终止。

某些阻塞库方法支持中断。

每个线程已经有一个布尔标志中断状态,你应该利用它。可以像这样实现:

public void run() {
   try {
      while (!interrupted()) {
         // ...
      }
   } catch (InterruptedException consumed)
      /* Allow thread to exit */
   }
}

public void cancel() { interrupt(); }

代码源自《Java并发编程实践》。由于cancel()方法是公开的,你可以让另一个线程调用该方法来实现你的目的。


如果您作为插件或脚本运行不受信任的代码,该怎么办?Java具有用于不受信任代码的嵌入式沙盒。但是,该沙盒是无用的,因为它允许在没有强制停止的情况下工作。想象一下,您正在使用Java编写浏览器。能够终止任意页面脚本的能力是无价的。 - ayvango
1
@ayvango 然后你必须在自己的沙盒中运行该脚本。Java沙盒保护机器免受应用程序的影响,而不是应用程序的各个部分相互之间的影响。 - David Schwartz
@DavidSchwartz,您的意思是我应该使用Java以外的其他平台吗? - ayvango
@ayvango 你仍然可以使用Java沙盒来保护计算机免受应用程序的影响。但是,如果您想保护应用程序的某些部分免受其他部分的影响,则需要选择一些可以实现该功能的工具。 - David Schwartz
1
我尝试过这个方法,但认为“cancel()”是从不同的线程调用的,而“interrupt()”在Runnable类(Java 8)上不可用。最终,我将线程句柄存储到“volatile Thread runningThread”中(感谢提供volatile点),并在“cancel()”中调用“runningThread.interrupt()”。 - Dazed
显示剩余2条评论

22

一种方法是设置一个类变量并将其用作标志。

Class Outer {
    public static volatile flag = true;

    Outer() {
        new Test().start();
    }
    class Test extends Thread {

        public void run() {
            while (Outer.flag) {
                //do stuff here
            }
        }
    }

}

在上面的示例中设置一个外部类变量,即将flag设置为true。将其设置为false以“终止”线程。


2
只是一个小提示:当线程运行并且没有被卡住时,变量作为标志才有效。Thread.interrupt()应该可以使线程从大多数等待条件(如wait、sleep、网络读取等)中解脱出来。因此,您永远不应该捕获InterruptedException以使其正常工作。 - ReneS
14
这不太可靠;将“flag”声明为volatile,以确保它在任何地方都能正常工作。内部类不是静态的,因此标志应该是实例变量。应该在访问器方法中清除标志,以便执行其他操作(如中断)。名称“flag”不具描述性。 - erickson
2
我不太理解“while”循环在run方法中的作用。这难道不意味着run方法中的所有内容都将被重复执行吗?这显然不是我们最初想要线程执行的操作 :( - user197762
2
+1 在执行 while (!Thread.currentThread().isInteruppted()) 时是首选的。 - Toby
2
两种情况都会失败,例如在while{// open ext process}中打开外部进程并且该进程挂起,现在既不会中断线程也不会到达结尾以检查布尔条件,你就会被卡住...试着使用java.exec启动一个python控制台,并尝试在不写exit的情况下重新获得控制权,看看是否有一种方法可以杀死该进程并退出...没有办法摆脱这种情况... - Space Rocker
显示剩余2条评论

13

根据已经累积的评论,我想添加几个观察结果。

  1. Thread.stop()会停止一个线程,但前提是安全管理器允许该操作。
  2. Thread.stop()是危险的。尽管如此,在JEE环境下,如果您无法控制被调用的代码,则可能是必需的;请参见为什么Thread.stop被弃用?
  3. 永远不要停止容器工作线程。如果您希望运行有可能挂起的代码,请(谨慎地)启动新的守护线程并监视它,必要时终止它。
  4. stop()调用线程上创建一个新的ThreadDeathError错误,然后在目标线程上抛出该错误。因此,堆栈跟踪通常是无用的。
  5. 在JRE 6中,stop()会与安全管理器进行检查,然后调用stop1()再调用stop0()stop0()是本地代码。
  6. 截至Java 13,Thread.stop()尚未被移除,但在Java 11中移除了Thread.stop(Throwable)。(邮件列表JDK-8204243

12

有一种方法可以做到这一点。但是,如果你不得不使用它,那么要么你是个糟糕的程序员,要么你正在使用由糟糕的程序员编写的代码。因此,你应该考虑停止成为一个糟糕的程序员或停止使用这个糟糕的代码。 这个解决方案仅适用于没有其他方法的情况。

Thread f = <A thread to be stopped>
Method m = Thread.class.getDeclaredMethod( "stop0" , new Class[]{Object.class} );
m.setAccessible( true );
m.invoke( f , new ThreadDeath() );

2
完全没有理由这样做,因为即使它已被弃用,仍然可以调用公共的 Thread.stop - Lii
1
@Lii Thread.stop的功能与之相同,但它还会检查访问和权限。 使用Thread.stop是相当明显的,我不记得为什么我使用Thread.stop0而不是它。也许Thread.stop在我的特殊情况下(Java 6上的Weblogic)无法正常工作。或者可能是因为Thread.stop已被弃用并引发警告。 - VadimPlatonov
1
在我的情况下,这是停止无限运行线程的唯一方法。由于某种原因,.stop() 没有停止线程,但 stop0() 做到了。 - fiffy

8

我会选择使用Thread.stop()方法。

例如,如果您有一个长时间运行的操作(比如一个网络请求)。 假设您正在等待响应,但是它可能需要一段时间而且用户已经导航到其他UI上。 这个等待线程现在是a)无用的b)潜在问题,因为当它获得结果时,它完全是无用的,并且将触发可以导致许多错误的回调。

所有这些都可以进行CPU密集型的响应处理。而作为开发人员,您甚至无法停止它,因为您不能在所有代码中抛出if (Thread.currentThread().isInterrupted())

所以无法强制停止线程是很奇怪的。


如果网络操作已经可以安全中止,只需中断线程即可。如果网络操作不可安全地中止,则无法安全地调用 Thread.stop()。你不是在投票支持 Thread.stop(),而是要求每个实现可能需要长时间才能完成的操作的人将其变得可以安全地中止。这可能是一个好主意,但与实现 Thread.stop() 作为请求安全中止的方式无关。我们已经有了 interrupt - David Schwartz
1
"因此,无法强制停止线程是很奇怪的。直到你深入研究(就像Java设计者所做的那样),并得出结论:没有技术上可行的解决方案比废弃stop更糟糕。" - Stephen C

5
这个问题相当模糊。如果你的意思是“如何编写程序以便在我想要停止时停止线程”,那么其他不同的回答可能会有帮助。但是,如果你的意思是“我有一个紧急情况,无法立即重启服务器,我只需要特定的线程停止运行,不管结果如何”,那么你需要一个干预工具来匹配监视工具,例如 jstack
为此,我创建了jkillthread。请参阅其使用说明。

谢谢!正是我想要的! - Denis Kokorin

4
“杀死线程”不是正确的短语。有一种方法可以实现优雅地完成/退出线程:
我使用的可运行方式:
class TaskThread implements Runnable {

    boolean shouldStop;

    public TaskThread(boolean shouldStop) {
        this.shouldStop = shouldStop;
    }

    @Override
    public void run() {

        System.out.println("Thread has started");

        while (!shouldStop) {
            // do something
        }

        System.out.println("Thread has ended");

    }

    public void stop() {
        shouldStop = true;
    }

}

触发类:
public class ThreadStop {

    public static void main(String[] args) {

        System.out.println("Start");

        // Start the thread
        TaskThread task = new TaskThread(false);
        Thread t = new Thread(task);
        t.start();

        // Stop the thread
        task.stop();

        System.out.println("End");

    }

}

2
应该在 shouldStop 变量上添加 volatile 关键字,以防编译器使用线程本地存储进行优化。 - Oleksii Kyslytsyn
如果“// do something”是一个需要很长时间才能完成的阻塞任务,那么这种方法就行不通了。 - Robber

4

当然,有一种情况是你正在运行某种不完全可信的代码。(我个人通过允许上传的脚本在我的Java环境中执行来实现这一点。是的,安全警报响遍了整个应用程序,但这是应用程序的一部分。)在这种不幸的情况下,你首先只是希望通过要求脚本编写者尊重某种布尔值的运行/不运行信号来解决问题。你唯一的体面备选方案是,在线程运行时间超过某个超时时间后调用线程的停止方法。

但是,这只是“体面”,而不是绝对的,因为代码可能会捕获ThreadDeath错误(或任何你明确抛出的异常),并且不像一个有礼貌的线程应该做的那样重新抛出它。所以,归根结底,据我所知没有绝对的备选方案。


据我所知,越来越多的服务正在变得混合,并使用托管环境来执行第三方代码(插件、脚本等),他们无法完全控制代码,因此完全排除线程停止似乎是不合理的,因为对于服务工程师来说,保持运行状态可能比非运行状态更好(由于挂起(占用线程)或忙碌的无限循环(占用核心))。 - Weipeng

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