为什么要从Lock(而不是使用“ new”运算符)创建条件?

时间:2019-10-01 09:14:59

标签: java concurrency

如果Condition本身可以单独使用,并且创建代码仅仅是:

    final ConditionObject newCondition() {
        return new ConditionObject();
    }

为什么不是这样创建的? 在类ArrayBlockingQueue中,构造函数中包含代码:

    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();

其中notEmptynotFull都是Condition类的实例。

3 个答案:

答案 0 :(得分:1)

摘自Condition的文档:

  

ConditionObject监视方法(waitnotifynotifyAll)分解为不同的对象,以产生具有多个等待集的效果每个对象,通过使用任意Lock实现将它们结合起来。 Lock替代了synchronized方法和语句的使用,而Condition替代了Object监视方法的使用。

     

条件(也称为条件队列或条件变量)为一种线程中止执行(“等待”)直到另一线程通知某些状态条件现在为真提供了一种方法。由于对该共享状态信息的访问发生在不同的线程中,因此必须对其进行保护,因此某种形式的锁与该条件相关联。等待条件提供的关键属性是它 atomic 会释放关联的锁并挂起当前线程,就像Object.wait一样。

     

Condition实例本质上绑定到锁。要获取特定Condition实例的Lock实例,请使用其newCondition()方法。

如前所述,Condition实例必须Lock实例 1 相关联。考虑到这一点,将Lock用作创建Condition实例的工厂是很有意义的,因为它暗示了两者之间的关系。可以强制执行此关系的另一种方法是给Condition一个接受Lock实例的构造函数,但是由于Condition也是一个接口,它不能声明构造函数。我还认为,在这种情况下,无参数工厂方法更易于使用。

注意::如果尚不清楚,则ReentrantLock类是Lock接口的实现,而ConditionObject类是实现的Condition界面。

尝试直接使用ConditionObject的另一个问题是它是AbstractQueuedSynchronizer 2 的内部类(即非静态嵌套类)。这意味着您将需要后一个类的实例才能创建前一个类的实例。但是,AbstractQueuedSynchronizer使用的ReentrantLock的实现是实现细节,并且不会向公众公开。换句话说,您无法调用ConditionObject的构造函数,这意味着创建实例的唯一方法是通过newCondition()

回顾一下,使用工厂方法创建Condition对象的原因至少有三个:

  1. 它使LockCondition之间的关系清晰可见。
  2. LockCondition都作为接口,您需要一种将ConditionLock关联的方法,而无需了解实现。否则,将无法"program to an interface"
  3. 由于ConditionObject是一个内部类,因此不能直接实例化-至少不能通过无法访问该封闭类实例的代码来实例化。

1。 Condition的方法仅在拥有Lock的情况下才有意义。就像线程必须在对象上synchronized才能合法调用该对象的监视方法(即,等待/通知)一样,线程必须拥有关联的Lock,然后才能合法调用该对象的监视方法。 Condition(即等待/信号)。

2。还有AbstractQueuedLongSynchronizer声明了自己的ConditionObject内部类。尽管该类与AbstractQueuedSynchronizer声明的类具有相同的名称,但实际上这两个是单独的类。

答案 1 :(得分:1)

“锁和条件”提供了一种以前(1.5版之前)只能通过synchronizationwait/notify和自定义条件代码使用的机制。

这是一个惯用的例子

synchronized(foo) {           // Lock
    while(!conditionMet)      // Condition
        foo.wait();           // Condition.signalAll();

    // Condition met, do something and notify
    conditionMet = false;
    foo.notifyAll();
}

该条件似乎与锁对象没有任何关系,但可以保证线程安全。如果不访问同步块内部的条件,则无法编写以上代码。 (您可以从非线程安全代码访问条件布尔值并弄乱事情,但总可以弄乱事情。)

整个ReentrantLock类实际上是Sync类对象的功能包装,它负责低级同步和条件的创建。条件绑定到特定的Lock(或Sync),以基本确保它们是线程安全的。正如Condition的Javadoc所述

  

因为访问此共享状态信息的方式不同   线程,必须对其进行保护,因此关联了某种形式的锁   有条件的。

答案 2 :(得分:1)

如Slaw所述,单独实例化一个新的Condition只是没有意义,因为它将失去与原始Lock对象的关联,这类似于从另一个Lock创建一个新Condition(因为java.util.concurrent .locks.Condition不能单独实例化)。

我们可以参考下面的两个示例以了解更多信息:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionTest {

    final ReentrantLock lock = new ReentrantLock();
    final Condition notEmpty = lock.newCondition();
    final Condition notFull =  lock.newCondition();

    String message;
    boolean ready;
    boolean isCompleted;

    public void consume() {

        ReentrantLock lock = this.lock;
        lock.lock();
        try { 

            while(!ready)
                notEmpty.await();

            System.out.println("Received message: " + message);
            ready = !ready; // reverse the "ready" state
            notFull.signal();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally { 
            lock.unlock();
        }

    }

    public void publish(String message) {
        ReentrantLock lock = this.lock;
        lock.lock();
        try { 
            while(ready)
                notFull.await();

            System.out.println("Adding message");
            this.message = message;
            ready = !ready; // reverse the "ready" state

            notEmpty.signal();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally { 
            lock.unlock();
        }       
    }


    public static void main(String[] args) {

        ConditionTest ct = new ConditionTest();

        Thread msgProducerThread = new Thread(() -> {
            List<String> messages = new ArrayList<String>();
            messages.add("Hello!");
            messages.add("here we get a message");
            messages.add("then we get another message");        

            messages.forEach(m -> ct.publish(m));

            ct.isCompleted = true;
        });

        Thread msgConsumerThread = new Thread(() -> {
            while (!ct.isCompleted)
                ct.consume();
        });

        msgProducerThread.start();
        msgConsumerThread.start();

    }   
}

然后我们将得到以下结果,这表示“锁定”和“条件”功能正常运行:

  

添加消息

     

收到的消息:你好

     

添加消息

     

收到的消息:当前项目已完成

     

添加消息

     

收到的消息:这是对新项目的估算

但是,如果我们尝试通过替换原始代码来使用单独的Condition:

Condition notEmpty = lock.newCondition();
Condition notFull =  lock.newCondition();

使用:

ReentrantLock lock2 = new ReentrantLock(fair);
ReentrantLock lock3 = new ReentrantLock(fair);
Condition notEmpty = lock2.newCondition();
Condition notFull =  lock3.newCondition();

然后,它将得到:

Adding message
Exception in thread "Thread-0" Received message: Hello!
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
    at ConditionTest.publish(ConditionTest.java:54)
    at ConditionTest.lambda$0(ConditionTest.java:75)
    at java.lang.Thread.run(Thread.java:745)
java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
    at ConditionTest.consume(ConditionTest.java:33)
    at ConditionTest.lambda$1(ConditionTest.java:83)
    at java.lang.Thread.run(Thread.java:745)

这意味着Condition和Lock之间没有相关的关联,从而导致抛出IllegalMonitorStateException。