繁体   English   中英

Thread.interrupt() 是邪恶的吗?

[英]Is Thread.interrupt() evil?

一位队友提出了以下主张:

Thread.interrupt()本质上是坏的,应该(几乎)永远不会被使用”。

我试图理解为什么会这样。

从不使用Thread.interrupt()是已知的最佳实践吗? 你能提供证据为什么它坏了/有问题,不应该用于编写健壮的多线程代码吗?

注意- 如果设计防腐剂“漂亮”,我对这个问题不感兴趣。 我的问题是 - 有问题吗?

简短版本:

从不使用 Thread.interrupt() 是已知的最佳实践吗?

没有。

你能提供证据为什么它是坏的/错误的,不应该用于编写健壮的多线程代码吗?

反之亦然:它对于多线程代码至关重要。

有关示例,请参见Java 并发实践中的代码清单 7.7。

更长的版本:

在这里,我们在一个特定的地方使用这个方法:处理InterruptedExceptions 这可能看起来有点奇怪,但代码如下:

try {
    // Some code that might throw an InterruptedException.  
    // Using sleep as an example
    Thread.sleep(10000);
} catch (InterruptedException ie) {
    System.err.println("Interrupted in our long run.  Stopping.");
    Thread.currentThread().interrupt();
}

这为我们做了两件事:

  1. 它避免了吃中断异常。 IDE 自动异常处理程序总是为您提供类似ie.printStackTrace();东西ie.printStackTrace(); 和一个活泼的“TODO:这里需要一些有用的东西!” 评论。
  2. 它在不强制此方法上检查异常的情况下恢复中断状态。 如果您正在实现的方法签名没有throws InterruptedException子句,则这是传播该中断状态的另一种选择。

一位评论者建议我应该使用未经检查的异常“强制线程死亡”。 这是假设我事先知道突然终止线程是正确的做法。 我不知道。

在上面引用的清单之前的页面上从 JCIP 引用 Brian Goetz:

任务不应对其执行线程的中断策略进行任何假设,除非它被明确设计为在具有特定中断策略的服务中运行。

例如,假设我这样做了:

} catch (InterruptedException ie) {
    System.err.println("Interrupted in our long run.  Stopping.");
    // The following is very rude.
    throw new RuntimeException("I think the thread should die immediately", ie);
}

我要声明的是,不管调用堆栈的其余部分和相关状态的其他义务如何,这个线程现在需要死亡。 我会试图绕过所有其他 catch 块和状态清理代码,直接进入线程死亡。 更糟糕的是,我会消耗线程的中断状态。 上游逻辑现在必须解构我的异常,以试图弄清楚是否存在程序逻辑错误,或者我是否试图将已检查的异常隐藏在一个模糊的包装器中。

例如,以下是团队中的其他人必须立即执行的操作:

try {
    callBobsCode();
} catch (RuntimeException e) { // Because Bob is a jerk
    if (e.getCause() instanceOf InterruptedException) {
        // Man, what is that guy's problem?
        interruptCleanlyAndPreserveState();
        // Restoring the interrupt status
        Thread.currentThread().interrupt();
    }
}

中断状态比任何特定的InterruptException更重要。 有关原因的具体示例,请参阅Thread.interrupt()的 javadoc:

如果此线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法或 join()、join(long)、join(long, int) 方法时被阻塞、sleep(long) 或 sleep(long, int) 等方法,那么它的中断状态将被清除,并且它会收到一个 InterruptedException。

如您所见,在处理中断请求时可以创建和处理多个 InterruptedException,但前提是该中断状态被保留。

我知道Thread.interrupt()被破坏的唯一方法是它实际上并没有做它看起来可能的事情 - 它实际上只能中断侦听它的代码。

但是,如果使用得当,在我看来它是一个很好的任务管理和取消的内置机制。

我推荐Java Concurrency in Practice以阅读有关正确和安全使用它的更多信息。

Thread.interrupt()的主要问题是大多数程序员不知道隐藏的陷阱并以错误的方式使用它。 例如,当您处理中断时,有一些方法可以清除标志(因此状态会丢失)。

此外,调用不会总是立即中断线程。 例如,当它挂在某个系统例程中时,什么都不会发生。 事实上,如果线程不检查标志并且从不调用抛出InterruptException的 Java 方法,则中断它不会有任何影响。

不,这不是马车。 它实际上是您如何在 Java 中停止线程的基础。 它在执行器框架中使用从java.util.concurrent中-见实施java.util.concurrent.FutureTask.Sync.innerCancel

至于失败,我从未见过失败,我广泛使用它。

未提及的原因之一是中断信号可能会丢失,这使得调用 Thread.interrupt() 方法毫无意义。 因此,除非 Thread.run() 方法中的代码在 while 循环中旋转,否则调用 Thread.interrupt() 的结果是不确定的。

我注意到,在线程ONE ,当没有可用的数据库连接时,我执行DriverManager.getConnection() (比如服务器关闭,因此最后这一行抛出SQLException )并且从线程TWO我明确调用ONE.interrupt() ,然后两个ONE.interrupted()ONE.isInterrupted()即使放在处理SQLExceptioncatch{}块中的第一行,也会返回false

当然,我通过实现额外的信号量解决了这个问题,但这很麻烦,因为这是我 15 年 Java 开发中的第一个此类问题。

我想这是因为com.microsoft.sqlserver.jdbc.SQLServerDriver的错误。 而且我进行了更多调查以确认对本native函数的调用在它自己产生的所有情况下都会消耗此中断,但在成功时保留它。

托梅克

PS我发现了类似的问题

PPS 我附上一个非常简短的例子,说明我在上面写的内容。 注册的类可以在sqljdbc42.jar找到。 我在 2015 年 8 月 20 日构建的类中发现了这个错误,然后我更新到了可用的最新版本(从 2017 年 1 月 12 日开始),但该错误仍然存​​在。

import java.sql.*;

public class TEST implements Runnable{
    static{
        try{
//register the proper driver
           Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        }
        catch(ClassNotFoundException e){
            System.err.println("Cannot load JDBC driver (not found in CLASSPATH).");
        }
    }

    public void run() {
        Thread.currentThread().interrupt();
        System.out.println(Thread.currentThread().isInterrupted());
//prints true
        try{
            Connection conn = DriverManager.getConnection("jdbc:sqlserver://xxxx\\xxxx;databaseName=xxxx;integratedSecurity=true");
        }
        catch (SQLException e){
            System.out.println(e.getMessage());
        }
        System.out.println(Thread.currentThread().isInterrupted());
//prints false
        System.exit(0);
    }

    public static void main(String[] args){
        (new Thread(new TEST())).start();
    }
}

如果您将一些完全不正确的内容作为"foo"传递给DriverManager.getConnection() ,您将获得消息“找不到适合 foo 的驱动程序”,并且第二个打印输出仍将如预期的那样正确。 但是,如果您传递了正确构建的字符串,但是,例如,您的服务器已关闭或您丢失了网络连接(这通常发生在生产环境中),您将看到java.net套接字超时错误打印输出和线程的interrupted()状态丢失。

问题不在于实现没有错误,而是您的线程在被中断时处于未知状态,这可能导致不可预测的行为。

暂无
暂无

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

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