Why can't await and signal methods be called directly on object of ReentrantLock. Why do I need Condition?

huangapple go评论72阅读模式
英文:

Why can't await and signal methods be called directly on object of ReentrantLock. Why do I need Condition?

问题

在旧的同步块中,我们使用相同的对象进行同步,还使用了 waitnotify 方法。因此,它们都可以指向相同的锁。有道理。

因此,当我使用 ReentrantLock 类时,为什么不能也使用同一个变量来调用 lockunlock 以及 awaitsignal 方法呢?为什么需要创建额外的 Condition 变量呢?

也就是说,为什么我需要这样做:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

void doSomething() {
    lock.lock();
    //一些代码
    condition.await();
    //一些代码
    lock.unlock();
}

而不是这样做:(这种类型的编码逻辑更合理吗)?

Lock lock = new ReentrantLock();

void doSomething() {
    lock.lock();
    //一些代码
    lock.await();
    //一些代码
    lock.unlock();
}

编辑:从文档中可以看出:**Condition 实例与锁固有地绑定在一起。**为什么设计成这样呢?为什么不只是拥有一个类型为 Lock 的变量,该变量将具有 await 和 signal 方法呢?

英文:

In old synchronized block, we used same object to synchronize on, also used wait and notify methods. So they can all refer to same lock. Makes sense.

So when I use class ReentrantLock, why can't I also use same variable to call lock, unlock as well as await and signal? Why do I need to make additional Condition variable?

That is, why I need to do this:

Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    void doSomething() {
        lock.lock();
            //some code
            condition.await();
            //some code
        lock.unlock();
    }

Instead of this: (wouldn't this type of coding be more logic)?

Lock lock = new ReentrantLock();

    void doSomething() {
        lock.lock();
            //some code
            lock.await();
            //some code
        lock.unlock();
    }

EDIT: from docs: A Condition instance is intrinsically bound to a lock.
Why design it that way? Why not just have one variable of type Lock which would have await and signal method?

答案1

得分: 6

LockCondition 的分离允许您在一个 Lock 上拥有多个 Condition,这在 Condition 中有文档记录:

ConditionObject 监视器方法(waitnotifynotifyAll)拆分成不同的对象,以实现在每个对象上具有多个等待集的效果(已添加强调),通过与任意 Lock 实现的结合使用。

以及 Lock

[Lock 实现] 允许更灵活的结构化,可能具有完全不同的属性,并且可能支持多个关联的 Condition 对象(已添加强调)。

有了这个能力,您可以执行以下操作:

这个方法带来了两个好处:

  1. 当推入一个元素时,只有等待弹出元素的线程会被通知,反之亦然。换句话说,只有等待特定 Condition 的线程会被通知。
  2. 您不必调用 signalAll(),这意味着只有一个线程被唤醒。
  3. (额外好处)提高了代码的可读性,至少在我看来是这样的。

以下是相同的 Stack 类,但使用 synchronized

请注意,现在每个线程都必须在相同的“条件”上等待,并且 每个等待的线程都会在任何事件发生时被通知。您必须通知所有等待的线程,因为您无法对哪些线程进行更精细的控制。

英文:

The separation of Lock and Condition allows you to have more than one Condition per Lock, which is documented by Condition:

>Condition factors out the Object monitor methods (wait, notify and notifyAll) into distinct objects to give the effect of having multiple wait-sets per object [emphasis added], by combining them with the use of arbitrary Lock implementations.

And Lock:

>[Lock implementations] allow more flexible structuring, may have quite different properties, and may support multiple associated Condition objects [emphasis added].

With that ability you can do things like:

import java.util.Objects;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Stack<E> {

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

  private final Object[] elements;
  private int size;

  public Stack(int capacity) {
    elements = new Object[capacity];
  }

  public E pop() throws InterruptedException {
    lock.lockInterruptibly();
    try {
      while (size == 0) {
        notEmpty.await();
      }
      @SuppressWarnings("unchecked")
      E element = (E) elements[--size];
      elements[size] = null;
      notFull.signal();
      return element;
    } finally {
      lock.unlock();
    }
  }

  public void push(E element) throws InterruptedException {
    Objects.requireNonNull(element);
    lock.lockInterruptibly();
    try {
      while (size == elements.length) {
        notFull.await();
      }
      elements[size++] = element;
      notEmpty.signal();
    } finally {
      lock.unlock();
    }
  }
}

This approach gives two benefits:

  1. When an element is pushed only a thread waiting to pop an element is signaled and vice versa. In other words, only the thread(s) waiting on a specific Condition are signaled.
  2. You don't have to invoke signalAll(), meaning only one thread is woken up.
  3. (Bonus) Improves readability of code, at least in my opinion.

Here's the same Stack class but using synchronized:

import java.util.Objects;

public class Stack<E> {

  private final Object lock = new Object();

  private final Object[] elements;
  private int size;

  public Stack(int capacity) {
    elements = new Object[capacity];
  }

  public E pop() throws InterruptedException {
    synchronized (lock) {
      while (size == 0) {
        lock.wait();
      }
      @SuppressWarnings("unchecked")
      E element = (E) elements[--size];
      elements[size] = null;
      lock.notifyAll();
      return element;
    }
  }

  public void push(E element) throws InterruptedException {
    Objects.requireNonNull(element);
    synchronized (lock) {
      while (size == elements.length) {
        lock.wait();
      }
      elements[size++] = element;
      lock.notifyAll();
    }
  }
}

Notice now that every thread has to wait on the same "condition" and that every waiting thread is notified any time anything happens. You have to notify all waiting threads because you have no finer control over which thread(s) are notified.

huangapple
  • 本文由 发表于 2020年8月10日 06:34:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/63331988.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定