想象一下,您在 Java 中有一个典型的生产者-消费者模式。为了提高效率,您希望使用notify()
而不是notifyAll()
在将新元素添加到队列时使用。如果两个生产者线程调用通知,是否保证两个不同的等待消费者线程将被唤醒?或者可能是两个notify()
s 在彼此之后不久触发导致同一个消费者线程排队等待唤醒两次?我找不到该部分是描述其工作原理的 API。java是否有一些原子内部操作来唤醒线程一次?
如果只有一个消费者在等待,那么第二个通知将丢失,那没问题。
想象一下,您在 Java 中有一个典型的生产者-消费者模式。为了提高效率,您希望使用notify()
而不是notifyAll()
在将新元素添加到队列时使用。如果两个生产者线程调用通知,是否保证两个不同的等待消费者线程将被唤醒?或者可能是两个notify()
s 在彼此之后不久触发导致同一个消费者线程排队等待唤醒两次?我找不到该部分是描述其工作原理的 API。java是否有一些原子内部操作来唤醒线程一次?
如果只有一个消费者在等待,那么第二个通知将丢失,那没问题。
我的回答有一些特定于实现的信息。它基于我对 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
可以等待workQueue
。 Consumer2
可以synchronized
在运行队列中被阻塞。如果将某些内容放入workQueue
并workQueue.notify()
调用。 Consumer2
现在被放入运行队列,但在谁最先出现的后面。 Consumer1
这是一个常见的实现。因此Consumer1
,从通知的项目中删除workQueue
项目Consumer2
。 Consumer2
如果队列为空,则必须再次测试,workQueue
否则remove()
将抛出,因为队列再次为空。有关比赛的更多详细信息,请参见此处。
同样重要的是要意识到虚假唤醒已被记录,因此while
循环可以防止线程在没有wait()
调用的情况下被唤醒。
综上所述,如果您可以按照其他答案中的建议使用 a 来减少生产者/消费者代码,BlockingQueue
那么您应该这样做。BlockingQueue
代码已经解决了所有这些问题。
是的,你所描述的可能会发生。
如javadoc中所述,notify
唤醒任意线程。因此,如果您的线程已完成并wait
在 next 之前调用过notify
,那么它就是唤醒的任意候选者之一。
我在多线程应用程序方面拥有丰富的经验,我发现我使用以下两种模式之一:
有多个睡眠线程需要在一个事件中唤醒,它们唤醒的顺序无关紧要。在这种情况下,我notifyAll
用来唤醒他们。
有一个睡眠线程需要在事件中唤醒。在这种情况下,我用notify
它来唤醒它。
如果我曾经遇到过有多个睡眠线程并且我只想唤醒其中一个的情况,我会使用不同的设计来实现这一点。基本上,我自己构建了一些东西,这样运行时环境就不会做出任意决定。我总是想确切地知道什么会醒来。
我将设计分解为这两个场景之一,或者我使用java.util.concurrent包中的某些内容。我从来没有遇到过虚假通知的问题,但我对用于锁定的对象也非常小心。我倾向于创建 vanillaObject
实例,其唯一目的是成为锁定操作的目标,但偶尔我会使用一个类类型已明确定义且在我控制之下的对象。
选择通知哪个线程“是任意的,由实现决定”
它几乎肯定不会是唤醒线程的“公平”(该术语用于计算机科学和并行)算法。同一个线程完全有可能连续两次被唤醒。另请注意,虚假通知也是可能的。
一般来说,我同意建议使用BlockingQueue实现而不是自己做的评论。
您可以使用ReentrantLock来获得公平的到达顺序策略。接口ReadWriteLock,获取生产者-消费者行为。ReentrantReadWriteLock类结合了这两种功能。
或者
您应该使用已实现此模式的ArrayBlockingQueue 。
int capacity = 10;
boolean fair = true;
new ArrayBlockingQueue(capacity, fair);