【问题标题】:Why can't await and signal methods be called directly on object of ReentrantLock. Why do I need Condition?为什么不能在 ReentrantLock 的对象上直接调用 await 和 signal 方法。为什么我需要条件?
【发布时间】:2020-08-09 22:34:52
【问题描述】:

在旧的同步块中,我们使用相同的对象进行同步,还使用了 waitnotify 方法。所以他们都可以引用同一个锁。有道理。

所以当我使用类 ReentrantLock 时,为什么我不能也使用相同的变量来调用 lockunlock 以及 await信号?为什么我需要添加额外的 Condition 变量?

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

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

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

而不是这个:(这种类型的编码不是更符合逻辑吗?

Lock lock = new ReentrantLock();

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

编辑:来自文档:条件实例本质上绑定到锁。 为什么要这样设计?为什么不只拥有一个具有等待和信号方法的 Lock 类型变量?

【问题讨论】:

  • 因为他们(Doug Lea)就是这样设计的。

标签: java multithreading locking locks


【解决方案1】:

LockCondition 的分离允许您在每个Lock 中拥有多个Condition,这由Condition 记录:

ConditionObject 监控方法(waitnotifynotifyAll)分解为不同的对象以产生每个对象具有多个等待集的效果 [强调添加],通过将它们与任意Lock 实现的使用结合起来。

还有Lock

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

有了这种能力,你可以做以下事情:

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();
    }
  }
}

这种方法有两个好处:

  1. 当一个元素被推送时,只有一个等待弹出元素的线程会收到信号,反之亦然。换言之,只有在特定 Condition 上等待的线程才会收到信号。
  2. 您不必调用signalAll(),这意味着只唤醒一个线程。
  3. (奖励)提高了代码的可读性,至少在我看来是这样。

这是相同的 Stack 类,但使用 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();
    }
  }
}

现在请注意,每个线程都必须等待相同的“条件”,并且每个等待的线程都会在发生任何事情时得到通知。您必须通知所有等待的线程,因为您无法更好地控制通知哪些线程。

【讨论】:

  • 谢谢,如果我使用“旧同步方式”,每个锁只能有一个条件,对吧?这是使用 Condition 类的优势吗?我没听错吧?
  • @Ana 是的,拥有一个单独的Condition 接口的好处是每个Lock 可以拥有多个Condition。我在答案的底部添加了一个使用synchronizedStack 版本。
猜你喜欢
  • 2017-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-26
  • 2023-04-10
  • 1970-01-01
  • 2020-04-03
  • 2019-12-17
相关资源
最近更新 更多