【问题标题】:Why do InterruptedExceptions clear a thread's interrupted status?为什么 InterruptedExceptions 会清除线程的中断状态?
【发布时间】:2010-03-26 13:52:42
【问题描述】:

如果线程在Object.wait()Thread.join() 中被中断,它会抛出一个InterruptedException重置线程的中断状态。即,如果我在 Runnable.run() 中有这样的循环:

while (!this._workerThread.isInterrupted()) {
    // do something
    try {
        synchronized (this) {
            this.wait(this._waitPeriod);
        }
    } catch (InterruptedException e) {
        if (!this._isStopping()) {
            this._handleFault(e);
        }
    }
}

调用interrupt()后线程会继续运行。这意味着我必须通过在循环条件中检查我自己的停止标志、重新抛出异常或添加 break 来显式地跳出循环。

现在,这并不完全是一个问题,因为这种行为有据可查,并且不会阻止我按照我想要的方式做任何事情。但是,我似乎不明白它背后的概念:为什么一旦抛出异常,线程就不再被认为是中断的?如果您使用interrupted() 而不是isInterrupted() 获得中断状态也会发生类似的行为,那么线程也只会出现一次中断。

我在这里做了什么不寻常的事情吗?例如,在循环外捕获InterruptedException 是否更常见?

(虽然我不是一个初学者,但我标记了这个“初学者”,因为这对我来说似乎是一个非常基本的问题,看着它。)

【问题讨论】:

  • 既然你问的是意见问题(“为什么会这样?”),你可能应该创建这个社区维基。
  • @Jonathan:这不是意见问题。查看 API 文档,很明显这背后必须有一个基本原理,所以我的问题有一个正确的答案。我接受的那个听起来很合理(线程安全中断处理)。

标签: java multithreading


【解决方案1】:

这个想法是一个中断应该被处理一次。如果一个显式的InterruptedException 没有清除“中断”标志,那么大多数InterruptedException 的捕获器将不得不显式清除该标志。相反,您可以通过自我中断(Thread.currentThread().interrupt())“清除”标志。 Java 的设计者采用了可以在大多数情况下节省击键的语义(即,您更经常想要清除标志而不是保持设置)。

【讨论】:

  • 我不同意正在发生的事情背后的逻辑,但他们肯定应该更好地命名该方法。
【解决方案2】:

不应该。这是一个不幸的设计缺陷,它使得依赖中断成为一项冒险的业务,因为库代码经常会捕获InterruptedException 而不会重置线程的中断标志并继续运行。如果您碰巧在该特定的损坏库代码运行时向您的线程发出中断信号,那么当您的代码重新获得执行控制权时,它将不知道发生了中断。

这只需要在您从代码调用的任何地方发生一次,因此为了能够中断线程,然后使用中断位从所述线程内部安全地控制您的流程,您需要100% 确保您调用的每一段代码都不会错误地清除被中断的位。当涉及到库时,这很难做到,但即使您可以考虑到您在代码中使用的每个库,这仍然不能解释buggy JRE code that can make the same mistake

只需要一个库(或 JRE!)作者不关心或考虑中断即可破坏需要它的代码逻辑这一事实表明,这是错误的默认操作。不关心线程中断位的人在捕获InterruptedException 后可能不会费心去重置它——也许他们甚至不知道它的存在!如果捕获InterruptedException 没有重置线程的中断状态,那么任何不知道中断位的人都会自动“做正确的事情”,并且不会对任何依赖中断的调用代码造成问题。任何需要清除它的人仍然可以手动清除它,但这将是一个明确的操作,它比捕获已检查的InterruptedException 异常的通常意外的副作用更可能是正确的。就目前而言,如果您依赖线程的中断位,那么调用堆栈中的任何人粗心地调用Thread.sleep() 都可能会毁了您的一天。

因此,大多数 Java 多线程代码只会复制带有“isRunning”实例字段的 Java 线程中断模型以及一些翻转它的机制作为解决方法。

【讨论】:

    【解决方案3】:

    这样写你的代码,你就不需要标志了:

    try {
        while (!this._workerThread.isInterrupted()) {
            // do something
            synchronized (this) {
                this.wait(this._waitPeriod);
            }
            // do something else
        }
    } catch (InterruptedException e) {
        // ignore ...
    }
    

    正如@Boyan 指出的那样,一般来说,压制中断异常是一个坏主意。在这种情况下,上下文将确定您是否应该将其压缩(如上)、设置中断标志(再次)或允许异常传播。除其他外,这取决于中断在您的应用程序中/对您的应用程序意味着什么。

    【讨论】:

    • 你不应该忽略它。在 90% 的情况下,您编写的代码会完全没问题,但有可能其他人正在等待这种中断并且清除标志会破坏他们的逻辑。所以为了安全起见,你必须在异常处理程序中调用Thread.currentThread().interrupt()
    【解决方案4】:

    这是因为InterruptedException 被认为是异常事件,在该事件中其他人试图从其外部停止线程。

    当你想真正中断一个线程时,你只需通过设置一个布尔值或类似的东西来打破它的循环条件。或者您使用.wait().notify() 从该线程内部。但是如果你在外部做wait()

    • 抛出异常以通知外部线程试图中断我或让我等待
    • 线程继续工作,因为它不接受来自其他线程的任何命令!但是异常的引发允许您添加特殊处理并做任何您想做的事情,也可以有效地停止线程。

    【讨论】:

    • 这是错误的。 Thread#isInterrupted 是您所说的可以用来循环的布尔值。中断线程会设置此位,但随后捕获 InterruptedException 会清除它,这就是这个问题的意义所在。如果您完全控制线程,那很好,因为您可以决定如何处理自己的线程,但是如果您正在编写不进行任何线程管理并且仅由其他代码调用的库或实用程序代码,那就是重置某人刚刚设置的中断位非常不礼貌。
    猜你喜欢
    • 2013-01-11
    • 2020-06-30
    • 2020-08-28
    • 2019-09-09
    • 2012-06-30
    • 2013-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多