简体   繁体   English

Thread.interrupt() 是邪恶的吗?

[英]Is Thread.interrupt() evil?

A teammate made the following claim:一位队友提出了以下主张:

" Thread.interrupt() is inherently broken, and should (almost) never be used". Thread.interrupt()本质上是坏的,应该(几乎)永远不会被使用”。

I am trying to understand why this is the case.我试图理解为什么会这样。

Is it a known best practice never to use Thread.interrupt() ?从不使用Thread.interrupt()是已知的最佳实践吗? Can you provide evidence why it is broken / buggy, and should not be used for writing robust multithreaded code?你能提供证据为什么它坏了/有问题,不应该用于编写健壮的多线程代码吗?

Note - I am not interested in this question if it's "pretty" from a design preservative.注意- 如果设计防腐剂“漂亮”,我对这个问题不感兴趣。 My question is - is it buggy?我的问题是 - 有问题吗?

Short version:简短版本:

Is it a known best practice never to use Thread.interrupt()?从不使用 Thread.interrupt() 是已知的最佳实践吗?

No.没有。

Can you provide evidence why it is broken / buggie, and should not be used for writing robust multithreaded code?你能提供证据为什么它是坏的/错误的,不应该用于编写健壮的多线程代码吗?

The opposite is true: it is critical for multithreaded code.反之亦然:它对于多线程代码至关重要。

See Listing 7.7 in Java Concurrency in Practice for an example.有关示例,请参见Java 并发实践中的代码清单 7.7。

Longer version:更长的版本:

Around here, we use this method in one specific place: handling InterruptedExceptions .在这里,我们在一个特定的地方使用这个方法:处理InterruptedExceptions That may seem a little strange but here's what it looks like in code:这可能看起来有点奇怪,但代码如下:

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();
}

This does two things for us:这为我们做了两件事:

  1. It avoids eating the interrupt exception.它避免了吃中断异常。 IDE auto-exception handlers always provide you with something like ie.printStackTrace(); IDE 自动异常处理程序总是为您提供类似ie.printStackTrace();东西ie.printStackTrace(); and a jaunty "TODO: Something useful needs to go here!"和一个活泼的“TODO:这里需要一些有用的东西!” comment.评论。
  2. It restores the interrupt status without forcing a checked exception on this method.它在不强制此方法上检查异常的情况下恢复中断状态。 If the method signature that you're implementing does not have a throws InterruptedException clause, this is your other option for propagating that interrupted status.如果您正在实现的方法签名没有throws InterruptedException子句,则这是传播该中断状态的另一种选择。

A commenter suggested that I should be using an unchecked exception "to force the thread to die."一位评论者建议我应该使用未经检查的异常“强制线程死亡”。 This is assuming that I have prior knowledge that killing the thread abruptly is the proper thing to do.这是假设我事先知道突然终止线程是正确的做法。 I don't.我不知道。

To quote Brian Goetz from JCIP on the page before the listing cited above:在上面引用的清单之前的页面上从 JCIP 引用 Brian Goetz:

A task should not assume anything about the interruption policy of its executing thread unless it is explicitly designed to run within a service that has a specific interruption policy.任务不应对其执行线程的中断策略进行任何假设,除非它被明确设计为在具有特定中断策略的服务中运行。

For example, imagine that I did this:例如,假设我这样做了:

} 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);
}

I would be declaring that, regardless of other obligations of the rest of the call stack and associated state, this thread needs to die right now.我要声明的是,不管调用堆栈的其余部分和相关状态的其他义务如何,这个线程现在需要死亡。 I would be trying to sneak past all the other catch blocks and state clean-up code to get straight to thread death.我会试图绕过所有其他 catch 块和状态清理代码,直接进入线程死亡。 Worse, I would have consumed the thread's interrupted status.更糟糕的是,我会消耗线程的中断状态。 Upstream logic would now have to deconstruct my exception to try to puzzle out whether there was a program logic error or whether I'm trying to hide a checked exception inside an obscuring wrapper.上游逻辑现在必须解构我的异常,以试图弄清楚是否存在程序逻辑错误,或者我是否试图将已检查的异常隐藏在一个模糊的包装器中。

For example, here's what everyone else on the team would immediately have to do:例如,以下是团队中的其他人必须立即执行的操作:

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();
    }
}

The interrupted state is more important than any specific InterruptException .中断状态比任何特定的InterruptException更重要。 For a specific example why, see the javadoc for Thread.interrupt() :有关原因的具体示例,请参阅Thread.interrupt()的 javadoc:

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.如果此线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法或 join()、join(long)、join(long, int) 方法时被阻塞、sleep(long) 或 sleep(long, int) 等方法,那么它的中断状态将被清除,并且它会收到一个 InterruptedException。

As you can see, more than one InterruptedException could get created and handled as interrupt requests are processed but only if that interrupt status is preserved.如您所见,在处理中断请求时可以创建和处理多个 InterruptedException,但前提是该中断状态被保留。

The only way I'm aware of in which Thread.interrupt() is broken is that it doesn't actually do what it seems like it might - it can only actually interrupt code that listens for it.我知道Thread.interrupt()被破坏的唯一方法是它实际上并没有做它看起来可能的事情 - 它实际上只能中断侦听它的代码。

However, used properly, it seems to me like a good built-in mechanism for task management and cancellation.但是,如果使用得当,在我看来它是一个很好的任务管理和取消的内置机制。

I recommend Java Concurrency in Practice for more reading on the proper and safe use of it.我推荐Java Concurrency in Practice以阅读有关正确和安全使用它的更多信息。

The main problem with Thread.interrupt() is that most programmers don't know about the hidden pitfalls and use it in the wrong way. Thread.interrupt()的主要问题是大多数程序员不知道隐藏的陷阱并以错误的方式使用它。 For example, when you handle the interrupt, there are methods which clear the flag (so the status gets lost).例如,当您处理中断时,有一些方法可以清除标志(因此状态会丢失)。

Also, the call will not always interrupt the thread right away.此外,调用不会总是立即中断线程。 For example, when it hangs in some system routine, nothing will happen.例如,当它挂在某个系统例程中时,什么都不会发生。 In fact, if the thread doesn't check the flag and never calls a Java method which throws InterruptException , then interrupting it will have no effect whatsoever.事实上,如果线程不检查标志并且从不调用抛出InterruptException的 Java 方法,则中断它不会有任何影响。

No, it's not buggy.不,这不是马车。 It actually is the basis of how you stop threads in Java.它实际上是您如何在 Java 中停止线程的基础。 It's used in the Executor framework from java.util.concurrent - see the implementation of java.util.concurrent.FutureTask.Sync.innerCancel .它在执行器框架中使用从java.util.concurrent中-见实施java.util.concurrent.FutureTask.Sync.innerCancel

As for failure, I've never seen it fail, and I've used it extensively.至于失败,我从未见过失败,我广泛使用它。

One reason not mentioned is that the interrupt signal can be lost which makes invoking the Thread.interrupt() method meaningless.未提及的原因之一是中断信号可能会丢失,这使得调用 Thread.interrupt() 方法毫无意义。 So unless your code in the Thread.run() method is spinning in a while loop the outcome of calling Thread.interrupt() is uncertain.因此,除非 Thread.run() 方法中的代码在 while 循环中旋转,否则调用 Thread.interrupt() 的结果是不确定的。

I noticed that when in thread ONE I execute DriverManager.getConnection() when there is no database connection available (say server is down thus finally this line throws SQLException ) and from the thread TWO I explicitely call ONE.interrupt() , then both ONE.interrupted() and ONE.isInterrupted() return false even if placed as the first line in the catch{} block where SQLException is handled.我注意到,在线程ONE ,当没有可用的数据库连接时,我执行DriverManager.getConnection() (比如服务器关闭,因此最后这一行抛出SQLException )并且从线程TWO我明确调用ONE.interrupt() ,然后两个ONE.interrupted()ONE.isInterrupted()即使放在处理SQLExceptioncatch{}块中的第一行,也会返回false

Of course I workarounded this issue implementing the extra semaphore but it is quite troublesome, as it is the very first such issue in my 15 years Java development.当然,我通过实现额外的信号量解决了这个问题,但这很麻烦,因为这是我 15 年 Java 开发中的第一个此类问题。

I suppose it's because of bug in com.microsoft.sqlserver.jdbc.SQLServerDriver .我想这是因为com.microsoft.sqlserver.jdbc.SQLServerDriver的错误。 And I investigated more to confirm that the call to the native function consumes this interruption in all cases it trhows its own, but preserves it when succeeded.而且我进行了更多调查以确认对本native函数的调用在它自己产生的所有情况下都会消耗此中断,但在成功时保留它。

Tomek托梅克

PS I found the analogous issue . PS我发现了类似的问题

PPS I enclose a very short example of what I'm writting above. PPS 我附上一个非常简短的例子,说明我在上面写的内容。 The registered class can be found in sqljdbc42.jar .注册的类可以在sqljdbc42.jar找到。 I found this bug in classes built on 2015-08-20 then I updated to the newest version available (from 2017-01-12) and the bug still exists.我在 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();
    }
}

If you pass something completely incorrect, as "foo" , to the DriverManager.getConnection() , you will obtain the message "No suitable driver found for foo", and the second printout will be still true as one would expect.如果您将一些完全不正确的内容作为"foo"传递给DriverManager.getConnection() ,您将获得消息“找不到适合 foo 的驱动程序”,并且第二个打印输出仍将如预期的那样正确。 But if you pass the correctly built string but, say, your server is down or you lost your net connection (that can generally occurr in the production environment), you will see the java.net socket timeout error printout and the thread's interrupted() state is LOST.但是,如果您传递了正确构建的字符串,但是,例如,您的服务器已关闭或您丢失了网络连接(这通常发生在生产环境中),您将看到java.net套接字超时错误打印输出和线程的interrupted()状态丢失。

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

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

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