可以多次通知唤醒同一个线程吗?

时间:2012-05-21 11:11:57

标签: java multithreading synchronization notify

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

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

4 个答案:

答案 0 :(得分:13)

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

  

如果两个生产者线程调用notify,是否可以保证唤醒两个不同的等待消费者线程?

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

  

或者可能是两个notify()在彼此之后不久被解雇会导致同一个comsumer线程排队等待两次唤醒?

没有。两个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()会抛出,因为队列再次为空。请参阅此处more details of the race

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

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

答案 1 :(得分:3)

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

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

我在多线程应用程序方面有丰富的经验,我发现我使用了这两种模式中的一种:

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

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

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

    我将设计分解为这两种情况之一,或者我使用java.util.concurrent包中的某些内容。我从来没有虚假通知的问题,但我也非常小心我用于锁定的对象。我倾向于创建vanilla Object实例,其唯一目的是成为锁定操作的目标,但偶尔我将使用一个对象,其类类型定义良好且受我控制。

答案 2 :(得分:2)

来自javadoc for notify()

选择要通知哪个线程“是任意的,并由执行决定”

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

一般情况下,我同意建议使用BlockingQueue实施而不是自己执行此操作的评论。

答案 3 :(得分:2)

您可以使用ReentrantLock获取公平的到达订单政策。接口ReadWriteLock,您获得生产者 - 消费者行为。班级ReentrantReadWriteLock结合了两种能力。

或者

您应该使用ArrayBlockingQueue,此模式已经实施。

int capacity = 10;
boolean fair = true;
new ArrayBlockingQueue(capacity, fair);
相关问题