可靠地停止无响应的线程 为什么?又快又脏复杂且安全相关

问题描述

我想知道如何在 Java 中停止一个没有响应的线程,这样它就真的死了。

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

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

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

作为外部观察者,我们无法看到正在发生的事情; Thread.stop() 始终静运行。最糟糕的是,通常的诊断将不再起作用(在 Corretto 1.8.0_275 Windows x64 上调试时试过这个):Thread.getState() 总是返回 RUNNABLE,不管是否成功杀死线程,Thread.isAlive() 也是如此{1}}(始终为真)。

解决方法

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

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

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

一些额外的阅读:

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

Counted loops

,

简而言之,没有 100% 可靠的方法可以按照您希望的方式阻止 Thread


为什么?

这是对不知道原因的其他人的解释,任何知道问题的人都可以跳过这一点。

线程如何强制终止的方式是使用 Thread 的中断状态。一个 Thread 应该以它的 interrupt() 方法被调用来终止,该方法将一个布尔标志设置为 true

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

无论如何,Thread 可以选择忽略这一点并继续运行。

此时可以调用 stop() 方法强制 Thread 终止。问题是这种方法会破坏并发性,可能会损坏对象,并且程序可能会在没有警告用户的情况下被破坏。见Why the stop() method is deprecated?


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

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


又快又脏

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

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

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

  • 终止程序并警告用户不要再次运行该 Thread
  • stop() 线程并希望一切顺利。

复杂且安全

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

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


相关

How do you kill a Thread in Java?

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

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

,

对我来说,如果进程因 isAlive 而完成,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 和堆栈跟踪的深度。

,

我认为这个问题描述了一个场景,这就是为什么 Thread.stop() 已被弃用多年的原因,但尚未删除......只是为了有一个“最后的选择”,只有在非常绝望时才使用并意识到所有负面影响。

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

好的,在测试过程中,你自己的代码可能会变得疯狂,你需要一个紧急中断——为此,如果你不想杀死整个 JVM(什么会更好),Thread.stop() 仍然足够好大多数情况下的选项)。但同样,您必须在开始测试之前将其构建到代码中......

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

,

这可能会打开一堆蠕虫,比如内存访问冲突,这会杀死 JVM 本身。

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

我认为 .finalize() 从 java11 或更早的版本开始实际上已被弃用,因此它可能不会对您有太大帮助。

如果您真的想在运行周期内确保运行时的安全,最好的办法是找到一种方法,在开始之前基本上绘制出您的配置,并设置监控工具,根据该图进行交叉检查和在查找注入的类和/或线程时监视运行时的完整性。 ...当然,这是假设您正在尝试防范 jvm 中的“类病毒”攻击...这并非闻所未闻,但仍然非常罕见。

如果您在 Internet 上运行一些代码,您可以简单地通过调用层次结构检查来解决问题,并找出产生问题线程的原因。

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...