【问题标题】:Can notify wake up the same thread multiple times?可以通知多次唤醒同一个线程吗?
【发布时间】:2012-05-27 21:16:14
【问题描述】:

假设您在 Java 中有一个典型的生产者-消费者模式。为了提高效率,您希望在将新元素添加到队列时使用notify() 而不是notifyAll()。如果两个生产者线程调用通知,是否保证会唤醒两个不同的等待消费者线程?或者可能是两个notify()s 在彼此之后不久触发导致同一个消费者线程排队等待唤醒两次?我找不到该部分是描述其工作原理的 API。 java是否有一些原子内部操作来唤醒线程一次?

如果只有一个消费者在等待,那么第二个通知将丢失,这没问题。

【问题讨论】:

  • 为什么不直接使用 BlockingQueue 实现?
  • 我同意毛里西奥的观点。如果可能,请避开 wait()/notify() - 这是马蜂窝的竞争条件、记录不充分的 API 和“虚假唤醒”。

标签: java multithreading synchronization notify


【解决方案1】:

来自javadoc for notify()

选择通知哪个线程“是任意的,由实现自行决定”

它几乎肯定不会是唤醒线程的“公平”(该术语用于计算机科学和并行)算法。同一个线程完全有可能连续两次被唤醒。另请注意,虚假通知也是可能的。

总的来说,我同意 cmets 建议使用 BlockingQueue 实现而不是自己执行此操作。

【讨论】:

    【解决方案2】:

    是的,您所描述的可能会发生。

    javadoc 中所述,notify 唤醒任意线程。因此,如果您的线程已完成并在下一个 notify 之前调用了 wait,那么它就是唤醒的任意候选者之一。

    我在多线程应用程序方面拥有丰富的经验,我发现我使用以下两种模式之一:

    1. 有多个睡眠线程需要在一个事件上唤醒,它们唤醒的顺序无关紧要。在这种情况下,我使用notifyAll 唤醒它们。

    2. 有一个睡眠线程需要在事件中唤醒。在这种情况下,我使用notify 来唤醒它。

    如果我遇到过有多个睡眠线程并且我只想唤醒其中一个的情况,我会使用不同的设计来实现这一点。基本上,我自己构建了一些东西,这样运行时环境就不会做出任意决定。我一直想知道究竟什么会醒来。

    我将设计分解为这两个场景之一,或者我使用来自java.util.concurrent 包的东西。我从来没有遇到过虚假通知的问题,但我对用于锁定的对象也非常小心。我倾向于创建 vanilla Object 实例,其唯一目的是成为锁定操作的目标,但有时我会使用类类型已明确定义且在我控制之下的对象。

    【讨论】:

      【解决方案3】:

      您可以使用ReentrantLock 获得公平的到达顺序政策。接口ReadWriteLock,获取生产者-消费者行为。 ReentrantReadWriteLock 类结合了这两种功能。

      或者

      你应该使用ArrayBlockingQueue,这个模式已经实现了。

      int capacity = 10;
      boolean fair = true;
      new ArrayBlockingQueue(capacity, fair);
      

      【讨论】:

        【解决方案4】:

        我的回答有一些特定于实现的信息。它基于我对 Sun JVM 和其他线程库行为的工作知识。

        如果两个生产者线程调用通知,是否保证两个不同的等待消费者线程将被唤醒?

        不,不是。不能保证会有任何消费者被唤醒。可以保证的是,如果有 2 个线程在等待,那么 2 个 不同的 线程将被放入运行队列。

        或者是两个notify()s 在彼此之后不久触发导致同一个消费者线程排队等待唤醒两次?

        没有。两次notify() 调用不会导致同一个消费者线程排队两次。然而,它可能会导致一个线程被唤醒并且可能没有其他线程在等待,因此第二个notify() 调用可能什么都不做。当然,线程可能已经被唤醒,然后又回去等待,然后以这种方式获得第二个 notify() 调用,但我认为这不是你要问的。

        java 是否有一些原子内部操作来唤醒线程一次?

        是的。 Thread 代码有多个同步点。一旦一个线程得到通知,它就会被移出wait 队列。未来对notify() 的调用将查看wait 队列并且找不到线程。

        还有一点重要。对于生产者/消费者模型,请始终确保您在 while 循环中测试条件。原因是存在竞争条件,消费者在锁上被阻塞但不等待条件。

         synchronized (workQueue) {
             // you must do a while here
             while (workQueue.isEmpty()) {
                 workQueue.wait();
             }
             workQueue.remove();
         }
        

        Consumer1 可能正在等待workQueueConsumer2 可以在 synchronized 处被阻止,但在运行队列中。如果将某些内容放入workQueue 并调用workQueue.notify()Consumer2 现在被放入运行队列,但在 后面 Consumer1 谁是第一个出现的。这是一个常见的实现。所以Consumer1 会从workQueue 中删除Consumer2 收到通知的项目。 Consumer2 必须再次测试 workQueue 是否为空,否则 remove() 将抛出,因为队列再次为空。在此处查看more details of the race

        同样重要的是要意识到虚假唤醒已被记录,因此while 循环可防止线程在没有wait() 调用的情况下被唤醒。

        所有这一切,如果您可以按照其他答案中的建议使用BlockingQueue 来减少您的生产者/消费者代码,那么您应该这样做。 BlockingQueue 代码已经解决了所有这些问题。

        【讨论】:

        • 完美答案,我的问题肯定有点不清楚,但你明白我想听的:-)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-04
        • 2020-08-31
        • 1970-01-01
        • 2021-09-02
        • 1970-01-01
        相关资源
        最近更新 更多