11

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

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

4

4 回答 4

13

我的回答有一些特定于实现的信息。它基于我对 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在运行队列中被阻塞。如果将某些内容放入workQueueworkQueue.notify()调用。 Consumer2现在被放入运行队列,但在谁最先出现的后面。 Consumer1这是一个常见的实现。因此Consumer1,从通知的项目中删除workQueue项目Consumer2Consumer2如果队列为空,则必须再次测试,workQueue否则remove()将抛出,因为队列再次为空。有关比赛的更多详细信息,请参见此处。

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

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

于 2012-05-21T12:40:07.200 回答
3

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

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

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

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

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

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

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

于 2012-05-21T11:48:50.443 回答
2

notify() 的 javadoc

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

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

一般来说,我同意建议使用BlockingQueue实现而不是自己做的评论。

于 2012-05-21T11:21:33.943 回答
2

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

或者

您应该使用已实现此模式的ArrayBlockingQueue 。

int capacity = 10;
boolean fair = true;
new ArrayBlockingQueue(capacity, fair);
于 2012-05-21T11:52:00.773 回答