可靠地停止无响应的线程

Reliably stopping an unresponsive thread

我想知道如何停止 Java 中的无响应线程,使其真正死掉。

首先,我很清楚 Thread.stop() 已被弃用以及为什么不应使用它;关于这个话题已经有很多很好的答案,cf。 [1][2]。因此,更准确地说,问题是,从技术上讲,是否真的有可能杀死一个代码不受我们控制但可能具有敌意且不响应中断的线程。

在最简单的情况下,恶意线程会 运行ning while(true);,但它也可能会耗尽内存或其他系统资源来造成更大的破坏。在该线程上调用 interrupt() 显然是无效的。改为调用 stop() 怎么样?

我在调试器中有 运行 这个,事实上,线程真的消失了。但这种做法靠谱吗?可以为这种情况准备敌对线程;想想 try{run();}catch(ThreadDeath t){run();} 它捕获了我们调用 stop() 时产生的 ThreadDeath 并再次递归调用它自己。

作为旁观者,我们看不到发生了什么; Thread.stop() 总是 运行 默默地。最糟糕的是,通常的诊断将不再起作用(在 Corretto 1.8 上调试时试过这个。0_275 Windows x64):Thread.getState() 总是 returns RUNNABLE无论是否成功终止线程,Thread.isAlive()(始终为真)也是如此。

这可能是不可能的,至少在所有情况下都不可靠。

如果我正确理解该机制(并且存在一些不确定性),如果代码以在执行期间没有安全点的方式执行(例如在计数循环中),则不可能JVM 向线程发出它应该停止的信号(线程从不轮询中断)。

在这种情况下,您需要杀死 JVM 进程,而不是线程。

一些额外阅读:

How to get Java stacks when JVM can't reach a safepoint

Counted loops

这可能会引发一系列蠕虫病毒,例如内存访问冲突,这会杀死 JVM itelf。

你可以做的是隔离线程,运行在其上.finalize(),然后强制 JVM 运行 GC 操作,例如 Runtime.gc(), System.runFinalization(),同时强制中断在该特定线程上以绕过它的复活行为。

我认为 .finalize() 自 java11 或更早的时候就已经被弃用了,所以它可能对你帮助不大。

如果您真的想在操作周期内确保您的 运行 时间安全,最好的办法是找到一种方法,在启动配置之前从根本上绘制出您的配置,并设置监控工具在查找注入的 类 and/or 线程的同时,对照该映射进行交叉检查并监控 运行 时间的完整性。 ...当然,这是假设您正试图在您的 jvm 中防范“类似病毒”的攻击...这并非闻所未闻,但仍然非常罕见。

如果您 运行 从 Internet 上下载一些代码,您可以简单地通过调用层次结构检查来解决问题,并找出是什么产生了有问题的线程。

注意:如果该侵入性线程正在调用循环回其调用者的本机 dll 代码,那么如果您将其地址 space 的部分标记为垃圾收集,您的 JVM 将会崩溃。

简而言之,没有 100% 可靠的方法可以按照您喜欢的方式停止 Thread


为什么?

这是给其他不知道为什么的人的解释,知道这个问题的人可以跳过这个。

线程强制终止的方式与Thread的中断状态有关。调用 Thread should be terminated with its interrupt() 方法将布尔标志设置为 true.

当中断标志设置为 true 时,Thread 应该几乎没有延迟地自行终止。

无论如何 Thread 可以选择简单地忽略它并继续 运行ning。

这是 stop() method can be called that forces the Thread to terminate. The problem is that this method messes up concurrency, can damage objects and the program can be corrupted without a warning for the user. See Why the stop() method is deprecated?


最后我想到了两种可能的方式,一种基本上是你的方式,另一种更安全但更复杂。

例如,包含拒绝终止的 Thread 的恶意第三方 .jar 可能会导致这些问题。


快速而肮脏

此解决方案并不完全安全,但根据使用情况,这可能是可以接受的,除非您真的喜欢安全性。

尝试先调用 Thread 上的 interrupt() 方法并给它一点时间来终止。

如果 Thread 没有响应,您可以:

  • 终止程序并警告用户不要 运行 再次 Thread
  • stop() 线程并希望最好。

复杂且安全

我能想到的最安全的解决方案是为 运行 中的 Thread 创建一个全新的进程。如果 Thread 不想在 [=13= 之后终止],您可以使用 System.exit(-1) 结束进程并让 OS 处理它。

您需要 Inter Process Communication 与其他进程通信,这使它变得更加复杂但也更安全。


相关

How do you kill a Thread in Java?

What is an InterruptedException in Java?免责声明:我已经回答了)

What does java.lang.Thread.interrupt() do?

我认为这个问题描述了一个场景,这就是为什么 Thread.stop() 自古以来就被弃用,但尚未被删除的原因……只是为了有一个 'last resort option',仅在被使用时使用真的很绝望,并且意识到所有的负面影响。

但是对 Thread.stop() 的调用必须以某种方式构建到代码中,就像人们可能想到的任何替代方案一样——那么为什么不直接修复线程的代码呢?或者,如果这是不可能的,因为该代码带有没有源代码的第三方库,为什么不替换该库呢?

好的,在测试期间,您自己的代码可能会乱码,您需要紧急中断 - 为此,如果您不想杀死整个 JVM(什么是在大多数情况下是更好的选择)。但是同样,您必须在开始测试之前将其构建到代码中……

但是在生产中,不应该有一个线程在接收到中断时不停止。所以应该不需要更换Thread.stop().

对我来说 isAlive returns 如果进程因 Thread.stop 而结束,则为 false。

我做了下面的例子,它成功地杀死了错误的线程。

import java.util.Arrays;
public class BrokenThreads{
    static boolean[] v = { true };
    public static void call(){
            try{
                while(true){
                    Thread.sleep(200);
                }
            
            } catch ( Throwable td){
                System.out.println("restarting");
                call();
            }   
    }
    public static void main(String[] args) throws Exception{
        
        Thread a = new Thread( BrokenThreads::call);
        
        a.start();
        Thread.sleep(500);
        System.out.println( Arrays.toString( a.getStackTrace() ) );
        while(v[0]){
            a.stop();
            System.out.println(a.getStackTrace().length);
            v[0] = a.isAlive();
        }
        System.out.println("finished normally");
        System.out.println( Arrays.toString( a.getStackTrace() ) );
    }
}

请注意,“getStackTrace”需要时间,您可以看到堆栈跟踪随着递归调用的进行而累积,直到两次停止足够快以结束线程。

这使用两种技术来查看线程是否已停止。 isAlive 和堆栈跟踪的深度。