有没有一种好的方法来强制停止Java线程?

19

我想立即停止一个Java线程,但每次尝试停止线程时,它总是需要一些时间才能停止。是否有一种好的方法可以强制Java线程立即停止?


@MohamedSaligh 调用中断并不会导致线程停止。它只是设置了中断标志。仍然由运行在该线程中的代码负责检查该标志。 - bhspencer
7个回答

29

没有一种好的方法可以立即停止一个线程。

  • Thread.stop(),但它是危险的和已弃用。除非:

    1. 您完全了解使其危险的问题,

    2. 您已经彻底分析了您的代码并确定了问题不适用和/或风险可接受,

    3. 您不关心应用程序在方法被删除时可能会停留在某个Java版本上1

  • 曾经有Thread.stop(Throwable),但同样很危险2已弃用在Java 11中已被删除3

  • Thread.interrupt(),但不能保证线程会快速停止,甚至根本不会停止。行为将取决于应用程序线程代码是否已设计为注意并正确响应中断。 (请参见下文。)

  • 有一种方法是编写线程以定期检查标志;请参阅此答案。 (请参见下文。)


"check a flag"和interrupt()的方法基本相同。在两种情况下,期望被中断的线程需要定期检查;例如通过调用interrupted()isInterrupted()或通过检查标志。
两种方法之间的区别在于interrupt()机制将在代码等待notify(), join()等或在I/O操作中阻塞时起作用。由于这个原因,并且因为interrupt是一种被广泛接受的应用程序无关的方式,通常优先实现特定于应用程序的flag机制。

1 - 在Java 18预发布的javadocs中,Thread.stop()现在被标记为“即将删除”。
2 - 可能更甚。例如,如果您执行了Thread.stop(new IOException()),一些调用堆栈上方的代码可能会轻松捕获异常并破坏您的“停止”功能。
3 - 您仍然可以通过反射调用私有的Thread.stop0(Throwable)方法来使用旧的“带有异常的停止”功能;请参见https://dev59.com/JG435IYBdhLWcg3wvyw5#67077495。最好避免这样做。


刚刚点赞了,但最后一段说如果被中断的线程在I/O上被阻塞,那么中断会起作用?这是正确的吗? - Nathan Hughes
@NathanHughes - 我非常确定。IO操作应该抛出InterruptedIOException异常。(如果无法中断在I/O中阻塞的线程,那将是非常糟糕的...) - Stephen C
我曾经认为某些类型的阻塞 I/O 是被处理的,而有些则不是,我需要进行研究,谢谢。 - Nathan Hughes
一些 I/O 在内核级别上是不可中断的...但这是操作系统的限制。如果一个 I/O 操作无法被中断,那么(在 Linux 上)发出 I/O 请求的进程是无法被终止的。 - Stephen C

24

有一种安全(而且快速)停止线程的好方法:

System.exit(0)

不幸的是,这会产生副作用,导致所有其他线程停止运行。


30
我不确定是否应该对答案进行反对投票(因为它是个恶作剧),还是点赞(因为从技术上讲它是正确的)。 - Christian Vielma
3
即使使用这种方法,也不能完全保证安全性,这取决于您的应用程序。如果您有其他线程应该在关闭之前将它们存储在内存中的数据存储下来,那么System.exit(0)调用将不会触发此存储行为。 @StephenC 您让我笑了,+1。 - Pieter12345
3
这是我在StackOverflow上找到的最“搞笑”的答案。我本来想给它点个踩,但后来我完全同意@christian vielma的看法。 - pdenti
3
我认为StackOverflow不是发布笑话的地方,这不是一个喜剧网站,请在评论区发笑话。如果你给出不严肃的答案,实际上你会浪费那些带着期望得到严肃回答的访问者的时间,从而降低整个网站的真正使命。 - vasilevich
3
在我的测试中,即使是 System.exit(0) 也不能总是关闭应用程序。 - user2199593
显示剩余4条评论

7

运行中的线程无法使用 Thread.Interrupt 停止,只有等待或阻塞的线程可以使用 Thread.Interrupt 停止。但可以使用共享变量来发出应该停止正在执行的信号。线程应该定期检查变量(例如:使用 while 循环),并以有序的方式退出。

private boolean isExit= false;

public void beforeExit() {
    isExit= true;
}

public void run() {
    while (!isExit) {

    }
}

这并不准确。线程可以检查中断标志的状态,如果任务实现正确,线程不必等待或休眠以使中断起作用。 - Nathan Hughes
在多线程场景中,isExit 布尔值的变化应该受到某种同步保护,以确保变化的可见性。例如,可以将其标记为 volatile - GPI

4
不管任何情况下都不要使用Thread.stop——如果你正在问这个问题,那么你还没有足够理解Java线程模型来安全地使用它。
对于好奇的人,详细信息在这里:Java Thread Primitive Deprecation 如果你真的精通Java并发编程,那么你会明白为什么你永远不应该使用它,即使在你自己的代码中完全安全,它也会让你的代码受到污染,并且无法被其他人使用。相反,你可能会使用布尔变量技巧或其他模式来告诉一个线程何时完成并退出。
永远不要使用Thread.stop。

我差点允许自己使用Thread.stop(),谢谢链接! - Aquarius Power

1
即使在JDK的后续版本中,您仍然可以使用特定异常停止线程。
由于已删除stop(Throwable),因此需要使用反射,但底层本机方法stop0(Throwable)(也由stop()调用)仍然存在,尽管不可见。
这是一个简单的方法,可通过抛出给定的异常来主动停止线程。
public static boolean stopWith(Thread t, Throwable e){
    try {
        Method m = Thread.class.getDeclaredMethod("stop0", Object.class);
        m.setAccessible(true);
        m.invoke(t, e);
        m.setAccessible(false);
        return true;
    } catch (Exception e1) {
        e1.printStackTrace();
    }
    return false;
}

这个方法适用于JDK 11,但是它代表着非法反射访问

如果你的代码使用了模块化,那么默认情况下你的模块不能通过反射访问非公共成员(因为java.base/java.lang对其他模块不是开放的)。因此会抛出一个java.lang.reflect.InaccessibleObjectException异常。 可以使用JVM的--add-opens标志覆盖默认开放java.base/模块,例如:--add-opens java.base/java.lang=your.module

如果你的代码没有使用模块化(即Java 8或之前的版本),由于为了向后兼容性而java.base/对所有未命名模块(ALL-UNNAMED)都是开放的,因此不会抛出任何异常。 但是会记录一条不可避免的警告信息:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by YourCalss (file:/path/to/your/class/) to method java.lang.Thread.stop0(java.lang.Object)
WARNING: Please consider reporting this to the maintainers of TestSop
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

请注意:1)Thread.stop()在Java 11中不是“隐形”的。 Thread.stop()仍然是public,仍然已过时。请查看Java 11 javadocs。2)在Java 11中,实际删除的是Thread.stop(Throwable)(而不只是“隐形”)。3)您所调用的是一个私有本地方法...stop0(Throwable)。这恰好是Thread.stop()调用的方法。 - Stephen C
重点是,如果您真的想在Java 11中调用Thread.stop(),则不必诉诸使用令人讨厌的、非可移植的、破坏抽象层的反射来实现。只有在需要使用特定于ThreadDeath之外的特定异常时,才需要使用stop0(Throwable)。(如果这样做,您需要考虑到某些内容可能会捕获您的异常...从而阻止“停止”。据我所知,这就是他们为什么删除stop(Throwable)的原因。) - Stephen C
是的,没有 stop(Throwable),你可以避免因错误而阻塞 stop()。但是你仍然可以通过捕获 ThreadDeathThrowable 来有意地阻塞。顺便说一下:已经在帖子中修正了错误的细节。 - Marco Torchiano

0

你的线程可以放在一个 while 循环中,并每次检查一个布尔变量。当你将变量设置为 false 时,循环将结束,线程也将结束。要在通过当前循环之前结束线程,可以使用 break 关键字。

这避免了使用已弃用的方法,如 Thread.stop()

private volatile boolean run = true;   // Needs be volatile ... or changes may
                                       // not be visible to all threads.

//Call this method to end your thread
void stopRunning(){
    run = false;
}

//This loop would go into the overided run() method

while(run){
    //Put your task here
}

线程将完成当前任务,然后自然停止。

如果您需要在线程完成任务之前停止它,您可以在循环中使用 if/else 检查运行变量,并像其他任何地方一样结束线程。

while(run){
    //Your code here

    /*Be sure your not stopping the task at a point that will harm your program.*/
    if(!run){break;}
}

0
在任何语言中,您都不能编写依赖于任何其他进程或线程的执行模式或时间的代码。您可以发送信号请求线程停止自身,或者您可以强制终止它,但是您仍然无法准确预测何时会完成这个肮脏的任务。您必须小心编写免疫所谓“竞态条件”的代码。

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