当捕获InterruptedException时,是否总是调用Thread.currentThread().interrupt()?

12

这篇IBM developerWorks文章提到:

“唯一可以忽略中断的情况是当你知道线程即将退出时。这种情况只会在调用可中断方法的类是Thread而不是Runnable的情况下出现[…]”。

到目前为止,我总是为我的线程实现 Runnable 接口。像这样提供一个 Runnable 实现:

public class View() implements Runnable {
    @Overload
    public void run(){
        Thread worker = new Thread(new Worker());
        worker.start();
        do{
            try{
                TimeUnit.SECONDS.sleep(3);
                updateView();
            }catch(InterruptedException e){
                worker.interrupt();
                // Thread.currentThread().interrupt();
                return;
            }
        }while(true);
    }

    protected void updateView(){
        // …
    }
}

在我的return;语句右边调用Thread.currentThread().interrupt();确实必要吗?return;本身不是已经执行了干净的退出了吗?为什么要这样做呢?文章中指出需要这样做是因为否则“[…] 在调用栈上方的代码将无法了解 […]”。相对于没有设置中断标志的线程,一个处于Thread.State.TERMINATED状态且带有中断标志的线程在应用程序关闭时的好处是什么?你能给我一个例子,在这个例子中,Runnable之外的代码检查了中断标志并有一个合理的原因吗?

顺便问一下,相对于实现Runnable接口,扩展Thread是否更好的代码设计呢?


请参考Extend Thread vs Implement Runnable,普遍的共识是实现Runnable - OldCurmudgeon
4个回答

11
它重置中断标志。这个JavaSpecialists newsletter更详细地介绍了这个令人困惑的主题。
在我的示例中,当我捕获InterruptedException后,我使用Thread.currentThread().interrupt()立即再次中断线程。为什么需要这样做?当异常被抛出时,中断标志会被清除,因此如果您有嵌套循环,您会在外部循环中引发问题。
所以如果您知道您的代码不会被其他组件使用,则不需要重新中断。不过,我真的不会进行这种微小的优化。谁知道您的代码将来会如何被使用/复用(甚至是通过复制/粘贴),因此我会为每个中断重置标志。

3

以下是一个例子,仅使用return是不够的:

public void doSomething1() {
  while (someCondition1()) {
    synchronized {
       try {
         this.wait();
       } catch (InterruptedException e) {
         return; // Should be Thread.currentThread().interrupt();
       }
    }
  }
}
public void doSomething2() {
  while (someCondition2()) {
    doSomething1();
  }
}

由于异常抛出会清除中断状态,所以下次执行doSomething1()时状态会被清除,线程不会终止。


1
我更喜欢扩展Thread,因为它能让你更好地理解线程在做什么,但这并不一定是更好的代码设计。
正如Brian所说,它会重置中断标志,但这并没有太多意义。在你的情况下,它将无法起作用,View-Thread将继续运行。
当中断线程时,标准程序是线程应该停止运行。它不会自动执行此操作,您必须实现一种方法来停止它一旦被中断。
使用内置功能有两个选项:
  • 将主循环放在InterruptedException的try块内。这样,当它被中断时,您将被从循环中抛出,并且该方法将退出。
  • 如果必须保存状态,则上述方法可能不好,因为它可能会破坏状态。作为替代方案,您可以设置中断标志(如所述,当它被抛出时重新中断线程)。
无论如何,在 while 循环中,您都必须检查线程是否被中断(使用!Thread.currentThread().isInterrupted()语句),否则它可能不会退出。您没有满足其中的第一种情况,也没有检查标志,因此在被中断后,您的 View 线程将继续运行。

扩展线程是一种不好的做法。参考:https://dev59.com/onRB5IYBdhLWcg3wtZMV - assylias
1
@assylias 没有任何地方说这是一种不好的做法。实现Runnable接口可能更适合某些问题,但正如我所说,为了理解它的工作原理,使用Thread是一个不错的选择。 - ddmps

0
作为一个程序员,我经常会在return;语句之前调用Thread.currentThread().interrupt();。尽管线程即将结束,但忽略中断是一个严重的问题,因此我总是遵循这个规则。
如果你确定这是run()方法完成和线程退出之前的最后一个返回语句,那么不是必须的。但是请参考上面的内容。为了后代,return;不会对中断标志进行任何操作。
问题是你的View类是否被包装。你确定当你返回时正在退出Thread吗?也许有人正在委托它。AOP可能已经实施了某种形式的仪器化。
调用它的好处是什么?文章指出,应该这样做,否则“调用堆栈上方的代码将无法找到它……”。
一般来说,当你的代码被某种包装代码(委托、AOP等)调用时,不要吞掉中断,因为这些包装代码需要中断标志。如果你吞掉了它,包装器将无法使用它。但在这种情况下,没有任何好处。
什么是Thread.State.TERMINATED状态下带有中断标志的线程与没有中断标志的线程在应用程序关闭时的好处?
没有好处。一旦线程退出,中断状态就毫无意义了。实际上,在线程死亡后,中断状态甚至看起来都没有被保留。
Thread thread = new Thread(new Runnable() {
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("caught");
        }
    }
});
thread.start();
thread.interrupt();
System.out.println(thread.isInterrupted());
thread.join();
System.out.println(thread.isInterrupted());

输出:

true
caught
false

你能给我一个例子,其中Runnable外部的代码检查中断标志以获得合理的原因吗?
我不能。除非有人在不知情的情况下将你的可运行对象包装在其他代码中,否则线程的run()方法之外没有代码。
如果您使用ExecutorService,则可能会发生这种情况,但在这种情况下,在运行作业之前会明确清除线程的中断状态。
所以,再次强调,原因是这是一个好的模式,这是软件工程中重要的事情。

我最近遇到的使用示例是 ExecutorService 的 shutDownNow 逻辑。 它正在检查中断状态以取消运行中的任务。 如果 Callable 或 Runnable 没有设置中断状态,则该任务将运行到完成,这可能不是期望的结果,特别是如果您现在正在优雅地停止线程池。 因此,任何未能响应中断的任务都可能永远无法终止 - Draukadin

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