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

非 Y 不嫁゛ 提交于 2020-08-26 10:34:46

问题


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:


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.



来源:https://stackoverflow.com/questions/63331988/why-cant-await-and-signal-methods-be-called-directly-on-object-of-reentrantlock

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!