条件与等待通知机制


69

与传统的等待通知机制相比,使用Condition接口/实现的优点是什么?在这里,我引用道格·李(Doug Lea)的评论:

条件将对象监视方法(wait,notify和notifyAll)分解为不同的对象,从而通过与任意Lock实现结合使用,从而使每个对象具有多个等待集。如果Lock替换了同步方法和语句的使用,而Condition替换了Object监视方法的使用。

我看到这是实现等待/通知机制的一种更面向对象的方式。但是,与前者相比有没有明显的优势?

Answers:


24

如上所述,关于条件接口有许多优点,一些重要的如下:

条件接口附带两个额外的方法

1)布尔值awaitUntil(日期截止时间)引发InterruptedException: 导致当前线程等待,直到发出信号或被中断或指定的截止时间过去为止。

2)awaitUninterruptible(): 使当前线程等待,直到发出信号为止。

如果当前线程进入此方法时设置了中断状态,或者在等待时被中断,它将继续等待,直到发出信号为止。当它最终从该方法返回时,其中断状态仍将被设置。

以上两种方法在对象类的默认监视器中不存在,在某些情况下,我们希望设置线程等待的截止日期,然后我们可以通过Condition接口实现此任务。

在某些情况下,我们不希望线程被中断,而希望当前线程等待,直到收到信号为止,然后我们可以使用条件接口中存在的awaitUninterruptible方法。

有关更多信息,条件接口Java文档:

http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/Condition.html#awaitUntil%28java.util.Date%29


2
如果系统时钟发生更改,则awaitUntil(日期截止日期)的行为将发生变化,而可能不等待(长时间inMilliseconds)
Piyush Mattoo 2014年

35

使用时,Condition: await()/signal()您可以区分哪个对象或一组对象/线程获得特定信号。这是一个简短的示例,其中一些线程(生产者)将获得isEmpty信号,而消费者将获得isFull信号:

private volatile boolean usedData = true;//mutex for data
private final Lock lock = new ReentrantLock();
private final Condition isEmpty = lock.newCondition();
private final Condition isFull = lock.newCondition();

public void setData(int data) throws InterruptedException {
    lock.lock();
    try {
        while(!usedData) {//wait for data to be used
            isEmpty.await();
        }
        this.data = data;
        isFull.signal();//broadcast that the data is now full.
        usedData = false;//tell others I created new data.          
    }finally {
        lock.unlock();//interrupt or not, release lock
    }       
}

public void getData() throws InterruptedException{
    lock.lock();
    try {
        while(usedData) {//usedData is lingo for empty
            isFull.await();
        }
        isEmpty.signal();//tell the producers to produce some more.
        usedData = true;//tell others I have used the data.
    }finally {//interrupted or not, always release lock
        lock.unlock();
    }       
}


很好的例子!您是否应该在发信号之前设置usedData = true / false?
AfterWorkGuinness 2014年

1
当使用wait()/ notify()时,您无需确定要通知哪些等待的对象/线程:您可以。
Piyush Mattoo 2014年

@AfterWorkGuinness>在发出信号之前,您不应该设置usedData = true / false信号代码之后直到锁块结束并释放它,所以顺序无关紧要
Grigory Kislin

1
@kasavbere因此,即使我使用相同的条件进行等待并在两种方法中进行通知,它也会起作用吗?不同的名称只是为了逻辑分离。
garg10may

34

最大的问题是等待/通知对于新开发人员来说容易出错。主要问题是不知道如何正确处理它们可能会导致模糊的错误。

  • 如果在wait()之前调用notify(),它将丢失。
  • 有时不清楚是否在同一对象上调用notify()和wait()。
  • 等待/通知中没有需要状态更改的任何内容,但是在大多数情况下,这是必需的。
  • wait()可能会虚假返回

Condition将此功能包装到专用组件中,但是其行为几乎相同。

在此之前还有一个问题,关于等待/无效发布的时间,还有很多很多。搜索[java] + wait + notify


3
您可以有多个共享同一锁的条件。例如docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…wait / notify仅具有一个“ wait set”,即您只能通知任何服务员,而不是特定的组。
彼得·劳瑞

6
除了第三点之外,我不确定Condition对其他方面有什么帮助。如果在等待之前调用,则会丢失对信号的调用,等待仍然可以提前返回,接口中没有任何内容指示状态必须更改。
Michael Krussel

1
@Peter Lawrey如果在wait()之前调用notify(),它将丢失。我不明白 这是什么意思?
gstackoverflow

2
@gstackoverflow notify()仅通知一个正在等待的线程。如果在notify()之后等待(wait),则等待不知道您早先调用了notify。
彼得·劳瑞

4
@PeterLawrey “条件是有状态的,并且会记住状态变化直到被消耗掉”我认为这是不正确的:如果在await()之前调用,则Condition的signal()也会丢失,就像调用时会丢失对象的notify()一样在wait()之前。我建议在回答中增加一些澄清,即条件对点1和点4毫无帮助(但在某种意义上说它们“更优”,因为它们更笼统-参见此答案
延斯·霍夫曼

3

要专门解决为什么拥有多个等待集是一个优势:

使用wait / notify通知线程是否正在等待不同的事物(常见示例是固定大小的阻塞队列,其中一些线程将事物放入队列中,并在队列已满时阻塞,而其他线程从队列中取出并阻塞当队列为空时),则如果您使用notify,从而导致调度程序从等待集中选择一个线程进行通知,则可能会遇到一些极端情况,即在特定情况下所选线程不希望被通知。例如,队列将通知要向队列中添加某些内容,但是如果所选线程是生产者并且队列已满,则它无法对该通知采取行动,而您宁愿交给消费者。使用内部锁定,您必须使用notifyAll,以确保通知不会丢失。

但是notifyAll每次调用都会引起混乱,每个线程都醒来并争夺锁,但是只有一个线程可以取得进展。其他线程都争先恐后地争夺锁,直到一次获得一个锁,并且很可能回到等待状态。它会产生很多争用,而不会带来太大的好处,因此最好能够使用notify并知道仅通知了一个线程,而该通知与该线程相关。

在这里,单独等待条件是一个很大的改进。队列可以根据条件调用信号,并知道它将仅唤醒一个线程,而该线程专门在等待该条件。

ConditionAPI文档有一个代码示例,该代码示例显示了如何对有界缓冲区使用多个条件,它说:

我们希望继续等待放置线程,并在单独的等待集中获取线程,以便我们可以使用仅在缓冲区中的项目或空间可用时才通知单个线程的优化。


0

除了其他公认的答案外-由于Condition与Lock对象关联,因此您的类中可以具有任意套Lock对象(重写,读取,写入),并具有与之关联的特定条件。然后,您可以使用这些条件集根据实现语义来同步类的不同部分。与等待通知imo相比,这提供了更大的灵活性和明确的行为

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.