71

与传统的等待通知机制相比,使用 Condition 接口/实现有什么优势?这里我引用 Doug Lea 的评论:

Condition 将对象监视器方法(wait、notify 和 notifyAll)分解为不同的对象,通过将它们与任意 Lock 实现的使用结合起来,使每个对象具有多个等待集的效果。Lock 代替了同步方法和语句的使用,Condition 代替了 Object 监视器方法的使用。

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

4

5 回答 5

37

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

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

Condition 将此功能包装到一个专用组件中,但它的行为大致相同。

有一个关于等待/nofity 的问题在此之前几分钟发布,还有很多很多搜索 [java]+wait+notify

于 2012-05-01T09:12:02.797 回答
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();
    }       
}
于 2012-05-02T04:10:21.240 回答
24

上面提到的条件接口有很多优点,其中一些重要的如下:

条件接口带有两个额外的方法,它们是:

1)boolean awaitUntil(Date deadline)throws InterruptedException : 导致当前线程等待,直到它被发出信号或中断,或指定的期限过去。

2)awaitUninterruptibly() : 使当前线程等待,直到它发出信号。

如果当前线程进入该方法时设置了中断状态,或者在等待中被中断,则继续等待直到signalled。当它最终从这个方法返回时,它的中断状态仍然会被设置。

上述两种方法在对象类中的默认监视器中不存在,在某些情况下我们想设置线程等待的截止日期,然后我们可以通过 Condition 接口来做到这一点。

在某些情况下,我们不希望线程被中断并希望当前线程等待它发出信号,然后我们可以使用 Condition Interface 中存在的 awaitUninterruptibly 方法。

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

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

于 2012-10-03T12:19:43.020 回答
4

为了具体说明为什么拥有多个等待集是一个优势:

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

但是 notifyAll 每次调用都会导致流失,每个线程都会唤醒并争夺锁,但只有一个线程可以取得进展。其他线程都在争先恐后地争夺锁,直到一次一个,它们可以获得锁并且很可能回到等待状态。它会产生很多争用而没有多大好处,最好能够使用通知并知道只通知一个线程,其中通知与该线程相关。

这是有单独的条件等待是一个很大的改进。队列可以在一个条件上调用信号,并且知道它只会唤醒一个线程,该线程专门等待该条件。

Condition的API 文档有一个代码示例,显示了对有界缓冲区使用多个条件,它说:

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

于 2017-04-21T15:27:50.633 回答
0

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

于 2020-02-23T19:29:17.940 回答