代码之家  ›  专栏  ›  技术社区  ›  Stephen

为什么要同时使用布尔值和interrupt()来表示线程终止?

  •  6
  • Stephen  · 技术社区  · 7 年前

    我在读关于如何在 https://docs.oracle.com/javase/7/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html ,这是我从问题中找到的链接 How do you kill a thread in Java?

    在第一个链接中,他们首先讨论使用 volatile 变量向要终止的线程发出信号。如果变量的值表示停止,线程应该检查该变量并停止操作(例如,如果是 null )。因此,要终止线程,可以将该变量设置为 无效的

    然后,他们讨论添加中断来帮助处理长时间阻塞的线程。他们给出了下面的stop()方法示例,该方法将volatile变量(water)设置为null,然后还抛出一个中断。

    public void stop() {
        Thread moribund = waiter;
        waiter = null;
        moribund.interrupt();
    }
    

    我只是想知道,你为什么两者都需要呢?为什么不仅要使用interrupt(),然后才能正确处理它?这对我来说似乎是多余的。

    3 回复  |  直到 7 年前
        1
  •  4
  •   Nathan Hughes    7 年前

    (这篇文章的第一部分是一般性的,可以说我没有注意问题的细节。跳到问题中讨论的techspec部分的末尾。)

    没有很好的技术原因。这部分是由于人为的限制,部分是由于混淆了api设计。

    首先考虑应用程序开发人员的首要任务是创建解决业务问题的工作代码。在匆忙完成工作的过程中,彻底学习像这样的低级API会迷失方向。

    第二,当你在学习的时候,有一种倾向,那就是达到一个足够好的状态,然后把它留在那里。线程和异常处理之类的内容是书中被忽视的老话题。中断是对书籍进行线程处理的书后主题。

    现在想象一个由多个技能水平不同、关注细节的人组成的代码库,他们可能忘记了从等待和睡眠中抛出InterruptedException会重置中断标志,或者interrupted与isInterrupted不同,或者InterruptedException也会重置中断标志。如果您有一个捕获IOException的finally块,那么您可能会错过InterruptedException是IOException的一个子类,并且可能会错过恢复中断标志的机会。也许匆忙中的人们决定见鬼去吧,我不能指望这面被打断的旗帜

    是这样吗?不

    • 手动卷起的标志不能像中断那样帮助短路等待或睡眠。

    • Java 5并发工具期望任务使用中断来取消。执行者希望提交给他们的任务检查是否有中断,以便优雅地退出。您的任务可能会使用其他组件,如阻塞队列。这个队列需要能够响应中断,使用它的东西需要知道中断。手动标记不适用于此,因为java 5类不知道它。

    必须使用中断,因为您使用的是预期的工具,但由于不可管理的技术性,您对标志值没有信心,这将导致这种代码。

    (rant结束,现在实际响应特定的techspec示例)

    好的,特别看看这篇techguide文章。有三件事很突出:

    1) 它根本没有利用干扰,只是为了缩短睡眠时间。然后,它只是抑制异常,而不必恢复标志或检查它。你 能够 使用InterruptedException通过在while循环外捕获它来终止,但这并不是它所做的。这似乎是一种奇怪的方式。

    2) 随着示例的充实,很明显该标志用于打开和关闭等待。有人可能会用中断来表达这个意思,但这不是惯用法。所以在这里有一面旗帜是可以的。

    3) 这是techguide中的玩具示例。并非所有Oracle内容都像techspecs或API文档那样具有权威性。有些教程有错误陈述或不完整。这可能是代码这样编写的原因,因为作者认为读者不会熟悉中断是如何工作的,最好尽量减少使用。众所周知,技术作家会做出这样的选择。

    如果我重写了它以使用中断,我仍然会保留该标志;我将使用中断来终止,并使用标志来暂停/恢复功能。

        2
  •  1
  •   statut    7 年前

    请看这个 documentation 。 你的线程应该检查 thread.isInterrupted() 状态,例如:

    Thread myThread = new Thread()  {
        @Override
        public void run() {
            while (!this.isInterrupted()) {
                System.out.println("I'm working");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //We would like also exit from the thread
                    return;
                }
            }
        }
    };
    

    当您想停止线程时,应该调用

    myThread.interrupt();
    

    此外,我们可以使用静态方法 Thread.interrupted() 这也会检查状态,但之后,该方法会清除它,您必须再次调用 myThread.interrupt() 再次设置状态。但我不建议使用 线中断() 方法

    这种方法有助于优雅地停止线程。

    因此,我也不认为有任何理由使用额外的 volatile 变量

    同样的行为可以通过其他 不稳定的 标记为@lexicore的答案,但我认为这是多余的代码。

        3
  •  0
  •   user1643723    7 年前

    @NathanHughes完全正确,但我将用几句话来重新表述他的长篇大论: 第三方代码

    这不仅仅是关于“愚蠢的初级开发人员”,在应用程序生命的某个时刻,您将使用 地块 第三方库的。这些库不会尊重您关于并发性的假设。有时,它们会无声地重置中断标志。使用单独的标志可以解决这个问题。

    不,你不能摆脱第三方代码,就这样一劳永逸。

    假设您正在编写一个库,例如ThreadPoolExecutor。库中的一些代码需要处理中断,甚至需要无条件地重置中断标志。为什么?因为之前的Runnable已经完成,而新的Runnable正在进行中。不幸的是,在这一点上,可能有一个陈旧的中断标志,这是针对等待,这是为谁呢?它可能是针对以前的Runnable或新的(尚未运行)Runnable的,没有办法判断。这就是为什么你添加 isCancelled 方法到FutureTask。并在执行new Runnable之前无条件重置中断标志。听起来很熟悉?

    Thread#interrupt 与实际工作单元完全分离,在该线程上运行,因此需要添加额外的标志。一旦你开始这样做,你就必须一直这样做——从最外层到最内层的工作单元。实际上,在线程上运行未知的用户提供的代码 Thread#interrupted 不可靠(即使 线程#中断 仍然可以正常工作!)