【问题标题】:Condition vs wait notify mechanism条件 vs 等待通知机制
【发布时间】:2012-05-10 20:30:20
【问题描述】:

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

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

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

【问题讨论】:

    标签: java multithreading concurrency


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 你可以有多个条件共享同一个锁。例如docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…wait/notify 只有一个“等待集”,即您只能通知任何服务员,而不是特定组。
    • 除了第三点之外,我不确定 Condition 有什么帮助。如果在 await 之前调用 signal 会丢失,await 仍然可以提前返回,接口中没有任何内容表明状态必须更改。
    • @Peter Lawrey 如果你在 wait() 之前调用 notify() 就会丢失。 我不明白。这是什么意思?
    • @gstackoverflow notify() 仅通知等待线程。如果你在 notify() 之后 wait(),则等待不知道你之前调用了 notify。
    • @PeterLawrey “条件是有状态的,并且在被消耗之前会记住状态更改” 我认为这是不正确的:条件的信号()也丢失了如果在 await() 之前调用,就像在 wait() 之前调用 Object 的 notify() 会丢失一样。我建议在答案中添加一些说明,即条件对第 1 点和第 4 点没有帮助(但从更普遍的意义上说,它们“更好” - 另见this answer
    【解决方案2】:

    当您使用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();
        }       
    }
    

    【讨论】:

    • - 谢谢,我在发布stackoverflow.com/questions/10407708/…后浏览了您的帖子
    • 很好的例子!你不应该在发信号之前设置 usedData=true/false 吗?
    • 当您使用 wait()/notify() 时,您无法决定通知哪些等待对象/线程:您可以这样做。
    • @AfterWorkGuinness > 你不应该在发信号之前设置 usedData=true/false 在信号代码之后直到锁定块结束并释放它,所以顺序无关紧要
    • @kasavbere 所以即使我在这两种方法中使用了相同的条件来等待和通知它也会起作用吗?不同的名称只是为了逻辑分隔。?
    【解决方案3】:

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

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

    1) 布尔型 awaitUntil(截止日期)抛出 InterruptedException : 使当前线程一直等待,直到它收到信号或中断,或指定的截止日期已过。

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

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

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

    在某些情况下,我们不希望线程被中断并希望当前线程等待它发出信号,然后我们可以使用 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

    【讨论】:

    • 如果系统时钟发生变化,awaitUntil(Date deadline) 的行为会发生变化,而 wait(long timeInMilliseconds) 可能不会发生
    【解决方案4】:

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

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

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

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

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

    我们希望在单独的等待集中继续等待 put 线程和 take 线程,以便我们可以使用优化,即在缓冲区中的项目或空间变为可用时只通知单个线程。

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2021-08-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多