Java: Thread.interrupted()和Thread.isInterrupted()使用上的区别是什么?

63

Java问题:据我所知,在线程内部检查线程是否收到中断信号的方法有两种,即Thread.interrupted()Thread.isInterrupted(),它们之间唯一的区别在于前者会重置内部的中断标志。

目前为止,我一直使用Thread.isInterrupted(),并且从未遇到过任何问题。然而,大多数教程都建议使用Thread.interrupted()。这是有特定原因吗?


1
Doug Lee广泛使用Thread.interrupted()。也许你应该发布使用isInterrupted()的代码,并让人们检查一切是否正常。 - irreputable
9个回答

100

interrupted()方法是static的,用于检查当前线程。而isInterrupted()方法是一个实例方法,用于检查调用它的Thread对象。

常见错误是在实例上调用静态方法。

Thread myThread = ...;
if (myThread.interrupted()) {} // WRONG! This might not be checking myThread.
if (myThread.isInterrupted()) {} // Right!

另一个区别是interrupted()还会清除当前线程的状态。换句话说,如果你连续两次调用它并且在两次调用之间线程没有被中断,第二次调用即使第一次调用返回了true,也会返回false

Javadocs告诉你这样重要的事情; 经常使用它们!


10
另一个不同之处在于interrupted()还会清除当前线程的状态,这使得interrupted()成为一个名称不当的方法。 - Vitalii Fedorenko

37
如果你使用interrupted,那么你所询问的是“自上次询问以来是否已被打断?” isInterrupted告诉你调用它的线程当前是否被打断。

7

有很多与InterruptedException相关的习语,但问题是关于显式检查中断状态。

我的理解是,实例方法isInterrupted应该很少使用 - 主要用于记录和调试等。它只给出给定线程上标志的快照,很快就可能过时。

正常的习惯是在编写希望在某个点可取消的任务时检查静态方法interrupted,此时它不会调用由于休眠或阻塞I/O调用等而引发InterruptedException的内容。如果看到设置了标志,您应该尽快停止当前计算,提前返回或抛出异常(例如InterruptedException)。

因此,举个例子,如果您的任务看起来像这样

void process(Things[] things) throws InterruptedException {
    for (Thing thing : things) {
        thing.twiddle(); // this call throws InterruptedException
    }
}

如果没有其他需要,那么您不需要做任何事情;如果有人在您的线程上调用Thread.interrupt,在当前或下一个twiddle调用期间,将抛出InterruptedException并停止您的任务。

但是,如果twiddle没有抛出InterruptedException并且通常无法在中途被中断怎么办?假设每个这样的调用需要100毫秒,但things.length可能为100。那么即使有人试图中断它,在某些情况下,process也可能被阻塞10秒钟,这在您的应用程序中可能是不可接受的。因此,您可以显式地检查中断:

void process(Things[] things) {
    if (Thread.interrupted()) {
        return;
    }
    for (Thing thing : things) {
        thing.twiddle();
    }
}

在这里,你可以看到为什么interrupted需要原子性地检查和清除标志:你正在使用它来确认是否已经收到了一条消息,并且有人礼貌地请求你尽快停止。在这种情况下,应该在请求后的大约100毫秒内停止。你也可以看到为什么这必须是一个静态方法,操作当前线程:只有在检查周围代码是否应该停止的上下文中才有意义。
当然,如果process的调用者假定它已经运行完毕,那么像这里展示的简单地return将会是误导性的。所以你可能想让process返回它完成处理的事物数量,或者直接向上抛出异常可能更合适。
void process(Things[] things) throws InterruptedException {
    if (Thread.interrupted()) {
        throw new InterruptedException();
    }
    for (Thing thing : things) {
        thing.twiddle();
    }
}

在这种情况下,调用者会收到一个(已检查的)异常,告知他们有人在中途请求停止处理。通常,调用者应该让异常被抛到调用堆栈上。
如果你无法停止当前任务但需要知道是否收到停止请求,例如为了缩短其余工作时间,你也可以重新中断自己:
void process(Things[] things) {
    boolean twiddleFully = true;
    if (twiddleFully && Thread.interrupted()) {
        twiddleFully = false;
        Thread.currentThread().interrupt();
    }
    for (Thing thing : things) {
        thing.twiddle(twiddleFully);
    }
}

在这里,我们可以更快地处理剩余的事情,但仍然完成循环,并打开中断标志,以便我们的调用者可以决定如何处理它。


7
在Java中,线程中断是一种建议性的操作。如果您调用Thread.interrupt()方法,则会设置标志并取消任何未完成的IO任务(这将引发InterruptedException异常)。但是,执行线程中的代码需要处理此操作。这样做被称为实现线程中断策略。
然而,由于线程的中断状态是共享的,因此任何此类处理都必须是线程安全的。如果您正在处理中断标志,不希望其他线程尝试使用它来执行某些操作。出于这个原因,Thread.interrupted()标志使其成为原子操作,因此当您想要说:“如果该线程被中断,那么我将处理它”时使用它。通常,这将涉及清理一些资源。完成后,您应该传播中断标志,以便调用者可以处理它。您可以通过再次调用Thread.interrupt()方法来实现此操作。

7
interrupted() 方法是一个类方法,它始终检查当前线程并清除中断 "标志"。换句话说,对 interrupted() 的第二次调用将返回 falseisInterrupted() 方法是一个实例方法;它报告调用它的线程的状态。此外,它不会清除中断标志。如果标志已设置,则在调用此方法后仍将保持设置。

4
以下是几个使用这些方法的示例:
  1. 如果你正在编写自己的线程池,可能需要检查其中一个管理的线程是否已被中断。这种情况下,你可以调用 managedThread.isInterrupted() 来检查它的中断状态。

  2. 如果你编写了自己的 InterruptedException 处理程序,并且不立即通过 Thread.currentThread().interrupt() 触发等效异常(例如,在异常处理程序之后你可能有一个 finally 块),你可能希望检查当前运行的线程是否已经被外部调用或 InterruptedException 中断。在这种情况下,你应该检查 Thread.interrupted() 的布尔值以检查当前线程的状态。

第二种方法只有在我担心某人已经在更低层次上编写了异常处理程序时才会对我有用,因为这样可能还吞掉了 InterruptedException。

清除Thread.interrupted()标志非常重要,因为您的代码可能会在排队到某个线程的可运行对象中被调用。如果interrupt()一个线程,那么可能会引入一个微妙的错误,因为该标志没有被重置。这也会导致下一个任务失败。当然,这是一个错误加上另一个错误。但最好遵循协议,以避免错误地保留标志。 - AminM

4

interrupted()方法是Thread类的静态方法,用于检查当前线程并清除中断标志。也就是说,对interrupted()方法的第二次调用将返回false。

isInterrupted()方法是实例方法,用于报告调用它的线程的状态。它不会清除中断标志。

如果标志被设置,在调用此方法后,它将保持设置状态。

Thread myThread = ...;
if (myThread.interrupted()) {} //error

Thread.interrupted()//right

if (myThread.isInterrupted()) {} // Right

1

这是一个老问题,经过查看答案后,我感觉仍然有一些信息缺失。以下是我填补那个缺失信息的尝试。

从Java 5开始,通常您只会间接地处理线程。实际上,从java.util.Executor框架生成的线程在库方法中处理。这些线程经常调用阻塞性质的实体,例如Future.get()。也就是说,get()会一直阻塞,直到结果可用为止。现在有一个重载形式的get(),它需要一个超时值,并且调用该方法意味着线程想要等待一个等于超时时间的期间,以便get()返回一个值,如果没有,那么可以通过Future.cancel()取消该任务。因此,这些方法认真处理中断,一旦它们嗅到中断,它们也会抛出已检查的InterruptionException。因此,调用者被迫处理InterruptionException。由于它们已经传播了传达中断状态的InterruptedException,因此让阻塞方法通过调用Thread.interrupt()来清除中断状态是有意义的。否则,将违反InterruptedException的契约。

然而,如果您正在处理原始线程(这当然现在不推荐),则在调用静态方法interrupted()时应该小心,因为如果您连续调用两次并且在线程两次调用之间未被中断,则第二次调用将返回false,即使第一次调用返回true。


实际上,当您调用Thread.interrupted()时,它会将值设置为false,以便下次调用时您将看到该值为false。请参见:http://www.yegor256.com/2015/10/20/interrupted-exception.html - Dave Yarwood
这意味着每当您调用 Thread.interrupted() 并且它返回 true 时,最好停止正在进行的操作,关闭并退出线程。如果无法退出,则最好通过 Thread.currentThread().interrupt() 重新中断线程,以便调用者可以处理中断。否则,调用者将不知道线程已被中断! - Dave Yarwood

1

为什么要中断?

在Java中,当你需要停止一个长时间运行的任务或关闭守护进程等情况时,中断线程是非常有用的。

如何中断

要中断线程,你需要在该线程上调用interrupt()方法。这是一个协作过程,因此你的代码必须准备好接受中断。例如:

myThread.interrupt();

负责任的代码

你的代码的职责是准备好应对任何中断。我甚至可以说,每当你有一个长时间运行的任务时,都要插入一些类似于这样的中断就绪代码:

while (... something long...) {

     ... do something long

     if (Thread.interrupted()) {
         ... stop doing what I'm doing...
     }
}

如何停止正在进行的操作?

您有几个选项:

  1. 如果您在 Runnable.run()中,只需返回或退出循环并完成该方法。
  2. 您可能在代码中深入了一些其他方法。此时抛出 InterruptedException可能是有意义的,因此您只需要这样做(保持标志清除)。
  3. 但也许在您的代码深处抛出 InterruptedException并不合适。在这种情况下,您应该抛出其他异常,但在此之前再次标记线程被中断,以便捕获异常的代码知道中断正在进行中。以下是一个示例:
private void someMethodDeepDown() {
    while (.. long running task .. ) {
          ... do lots of work ...

          if (Thread.interrupted()) {
             // oh no! an interrupt!
             Thread.currentThread().interrupt();
             throw new SomeOtherException();
          }
     }
}

现在异常可以传播并终止线程或被捕获,但接收代码希望能够注意到中断正在进行中。 我应该使用isInterrupted()还是interrupted() 你应该优先使用interrupted(),因为:
  1. 你的代码应该重置中断标志,因为如果不这样做,你使用的线程可能会回到具有中断状态的线程池,从而引起问题(当然,这是线程池代码中的错误,如果例如使用Executors.newFixedThreadPool(),你将不会得到那种行为。但其他线程代码可能会有它)。
  2. 正如另一个答案所述,清除中断标志表示你已经收到消息并正在采取行动。如果你让它保持为true,那么一段时间后,调用者可能会认为你不会及时响应它。
为什么使用interrupt()而不是我的代码中的其他标志?

中断是最好的中断机制,因为我们的代码可以做好准备。如果我们发现代码只是捕获和忽略InterruptException或者在其主体中没有检查interrupted(),那么我们可以纠正这些错误,并使我们的代码始终能够干净地中断,而不会在您的代码中创建奇怪的依赖关系非标准机制。

不幸的是,Joshua Block在他著名的书籍Effective Java, Second Edition中提出了相反的观点。但我认为启用interrupt()方法按预期工作要好得多。

Future.cancel()已经处理了这个问题,对吗?

Future cancel将任务从运行队列中删除。如果您的任务已经在运行,则不会停止它。因此,cancel()与中断是不同的概念。如Javadocs所述:

尝试取消执行此任务。如果任务已经完成、已被取消或由于其他原因无法取消,则此尝试将失败。如果成功,且在调用取消时此任务尚未启动,则此任务不应运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应中断执行此任务的线程以尝试停止任务。但是,如果 mayInterruptIfRunning 是开启的,则调用将生成一个中断。 https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/concurrent/Future.html#cancel(boolean)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接