繁体   English   中英

Thread.stop 和朋友在 Java 中安全吗?

[英]Are Thread.stop and friends ever safe in Java?

java.lang.Thread中的stop()suspend()resume()已被弃用,因为它们是不安全的 Oracle 推荐的解决方法是使用Thread.interrupt() ,但这种方法并不适用于所有情况。 例如,如果您调用的库方法未显式或隐式检查interrupted标志,则您别无选择,只能等待调用完成。

所以,我想知道是否有可能描述在线程上调用stop()是(可证明)安全的情况。 例如,它可以安全地stop()一个线程,什么也没做,但通话find(...)match(...)java.util.regex.Matcher

(如果有任何 Oracle 工程师正在阅读这篇文章......一个明确的答案将不胜感激。)

编辑:答案只是重申你不应该调用stop()的口头禅,因为它已被弃用,不安全,无论是什么都没有解决这个问题的重点。 我知道在大多数情况下它确实是不安全的,如果有可行的替代方案,您应该始终使用它。

这个问题是关于安全的子集情况。 具体来说,什么是子集?

这是我试图回答我自己的问题。

我认为以下条件应该足以使用Thread.stop()安全停止单个线程:

  1. 在线程停止的情况下,线程执行不得创建或改变其他线程可能可见的任何状态(即 Java 对象、类变量、外部资源)。
  2. 线程执行在其正常执行期间不得对任何其他线程使用notify
  3. 该线程不得startjoin其他线程,也不得使用stopsuspendresume与之交互。

(上面的术语线程执行涵盖了所有应用程序级代码和线程执行的所有库代码。)

第一个条件意味着停止的线程不会使任何外部数据结构或资源处于不一致状态。 这包括它可能在互斥锁中访问(读取)的数据结构。 第二个条件意味着可停止线程不能让其他线程等待。 但它也禁止使用除简单对象互斥锁之外的任何同步机制。

一个可停止的线程必须有办法将每个计算的结果传递给控制线程。 这些结果是由可停止线程创建/改变的,所以我们只需要确保它们在线程停止后不可见。 例如,结果可以分配给 Thread 对象的私有成员,并用一个标志“保护”,该标志由线程原子地表示“完成”。

编辑:这些条件非常严格。 例如,要安全停止“正则表达式评估器”线程,如果我们必须保证正则表达式引擎不会改变任何外部可见状态。 问题是它可能会这样做,具体取决于您实现线程的方式!

  1. Pattern.compile(...)方法可能会更新编译模式的静态缓存,如果他们这样做了,他们将(应该)使用互斥锁来做到这一点。 (实际上,OpenJDK 6.0 版本不缓存模式,但 Sun 可能会改变这一点。)
  2. 如果您试图通过在控制线程中编译正则表达式并提供预先实例化的Matcher来避免 1),那么正则表达式线程确实会改变外部可见状态。

在第一种情况下,我们可能会遇到麻烦。 例如,假设使用 HashMap 来实现缓存,并且在重组 HashMap 时线程被中断。

在第二种情况下,我们将确定提供Matcher尚未传递到其他线程,并提供了该控制器的线程没有尝试使用Matcher停止正则表达式匹配跟帖。

那么这给我们留下了什么?

好吧,我想我已经确定了理论上可以安全停止线程的条件。 我还认为理论上可以静态分析线程的代码(及其调用的方法),以查看这些条件是否始终成立。 但是,我不确定这是否真的实用。

这有意义吗? 我错过了什么吗?

编辑 2

当您考虑到我们可能试图杀死的代码可能不受信任时,事情会变得更加棘手:

  1. 我们不能依赖“承诺”; 例如,在不受信任的代码上的注释,它要么是可杀的,要么是不可杀的。

  2. 我们实际上需要能够阻止不受信任的代码做一些会使其无法杀死的事情……根据确定的标准。

我怀疑这将需要修改 JVM 行为(例如实现运行时限制哪些线程被允许锁定或修改),或者 Isolates JSR 的完整实现。 这超出了我认为的“公平游戏”的范围。

因此,让我们暂时排除不受信任的代码案例。 或者至少,承认恶意代码可以使自己无法安全地杀死,并将这个问题放在一边。

安全性的缺失来自于临界区的想法

Take mutex

do some work, temporarily while we work our state is inconsistent

// all consistent now

Release mutex

如果你吹走线程并且它恰好处于临界区,那么对象将处于不一致的状态,这意味着从那时起就不能安全使用。

为了安全地杀死线程,您需要了解该线程中正在执行的任何操作的整个处理过程,才能知道代码中没有这样的关键部分。 如果您使用的是库代码,那么您可能无法看到源代码并知道它是安全的。 即使今天是安全的,也可能不是明天。

(非常人为的)可能不安全的例子。 我们有一个链表,它不是循环的。 所有算法都非常灵活,因为我们知道它不是循环的。 在临界区,我们暂时引入了一个循环。 然后,在我们从临界区出来之前,我们会被吹走。 现在所有使用列表的算法永远循环。 没有图书馆作者肯定会这样做! 你怎么知道? 您不能假设您使用的代码编写得很好。

在您指向的示例中,肯定可以以可中断的方式编写 requreid 功能。 更多的工作,但可能是安全的。

我会拿一张传单:没有可在可取消线程中使用的对象和方法的文档子集,因为没有库作者想要做出保证。

也许有一些我不知道的东西,但正如java.sun.com所说,它是不安全的,因为该线程正在处理的任何东西都有被损坏的严重风险。 其他对象、连接、打开的文件……出于显而易见的原因,例如“不要先保存就关闭 Word”。

对于这个find(...)例子,我真的不认为简单地用一个无用的.stop()把它踢掉会是一场灾难......

一个具体的例子可能会有所帮助。 如果有人可以提出一个很好的替代方法来替代以下 stop 的用法,我会非常感兴趣。 重新编写 java.util.regex 以支持中断不算数。

import java.util.regex.*;
import java.util.*;

public class RegexInterruptTest {

    private static class BadRegexException extends RuntimeException { }
        final Thread mainThread = Thread.currentThread();
        TimerTask interruptTask = new TimerTask() {
            public void run() {
                System.out.println("Stopping thread.");
                // Doesn't work:
                // mainThread.interrupt();
                // Does work but is deprecated and nasty
                mainThread.stop(new BadRegexException());
            }
        };

        Timer interruptTimer = new Timer(true);
        interruptTimer.schedule(interruptTask, 2000L);

        String s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab";
        String exp = "(a+a+){1,100}";
        Pattern p = Pattern.compile(exp);
        Matcher m = p.matcher(s);
        try {
            System.out.println("Match: " + m.matches());
            interruptTimer.cancel();
        } catch(BadRegexException bre) {
            System.out.println("Oooops");
        } finally {
            System.out.println("All over");
        }
    }
}

有一些方法可以使用 Thread.stop() 相对稳定的无泄漏内存或文件描述符(FD 在 *NIX 上特别容易泄漏),但只有在您被迫管理 3rd 方代码时才应依赖它。 如果您可以控制代码本身,请不要使用它来实现结果。

如果我在 w/interrupt() 中使用 Thread.stop 和一些更多的hacks,比如添加自定义日志处理程序来重新抛出被捕获的 ThreadDeath,添加 unhandleExceltionHandler,运行到你自己的 ThreadGroup(同步),等等......

但这值得一个全新的话题。

但在这种情况下,是 Java Designers 告诉您的; 他们在语言方面比我们中的任何一个都更有权威:)

只是一个说明:他们中的很多人都很无知

如果我的理解是正确的,那么问题与同步锁没有被释放有关,因为生成的 ThreadInterruptedException() 向上传播堆栈。

认为这是理所当然的,它本质上是不安全的,因为您永远无法知道在调用和执行 stop() 的那一刻您是否碰巧处于任何“内部方法调用”中,是否有效地持有一些同步锁,然后是什么Java 工程师说的似乎是正确的。

我个人不明白的是为什么不可能释放任何同步锁,因为这种特殊类型的异常向上传播堆栈,从而传递所有 '}' 方法/同步块分隔符,这确实会导致任何锁被释放对于任何其他类型的异常。

我有一个用 java 编写的服务器,如果该服务的管理员想要“冷关机”,那么无论如何都必须能够停止所有正在运行的活动。 任何对象状态的一致性都不是问题,因为我要做的就是退出。 尽我所能。

没有安全的方法可以杀死线程。

也没有安全的情况的子集。 即使它在 Windows 上测试时 100% 工作,它也可能在 Solaris 下损坏 JVM 进程内存或在 Linux 下泄漏线程资源。

应该永远记住,在 Java 线程下面有一个真正的、本机的、不安全的线程。

该本机线程与本机、低级、数据和控制结构一起工作。 杀死它可能会使那些本机数据结构处于无效状态,无法恢复。

Java 机器无法考虑所有可能的后果,因为线程不仅可以在 JVM 进程中分配/使用资源,还可以在操作系统内核中分配/使用资源。

换句话说,如果本机线程库没有提供一种安全的方法来 kill() 一个线程,Java 就不能提供任何比这更好的保证。 我所知道的所有本地实现都表明,杀死线程是一项危险的业务。

通过构造适合您的问题的更复杂的并发控制,Java 同步原语可以提供所有形式的并发控制。

您提供的链接中明确给出了弃用的原因。 如果您愿意接受原因,那么请随意使用这些功能。

但是,如果您选择使用这些功能,您也接受了对这些功能的支持可能随时停止。

编辑:我将重申弃用的原因以及如何避免它们。

由于唯一的危险是可以被stop线程引用的对象可能会被破坏,因此只需在将String传递给Thread之前clone它。 如果不存在共享对象,则stop Thread之外的程序中对象损坏的威胁就不再存在。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM