我正在阅读有关 Condition 的信息java.util.concurrent.locks.Condition
。
条件将对象监视器方法(等待、通知和通知所有)分解为不同的对象,通过将它们与任意锁定实现的使用结合起来,使每个对象具有多个等待集的效果。
有人可以解释一下吗?
与普通同步块或方法相比,这有什么好处?
我正在阅读有关 Condition 的信息java.util.concurrent.locks.Condition
。
条件将对象监视器方法(等待、通知和通知所有)分解为不同的对象,通过将它们与任意锁定实现的使用结合起来,使每个对象具有多个等待集的效果。
有人可以解释一下吗?
与普通同步块或方法相比,这有什么好处?
一个锁可以与许多条件相关联。锁是一个“对象”,每个条件都是一个“等待集”。这允许独立条件共享临界区。例如,考虑有界生产者-消费者问题。解决它的一种方法是拥有一个保护队列的锁和两个独立的等待集:一个用于生产者,等待槽将项目放入队列,另一个用于等待项目获取的消费者。使用普通的旧APIsynchronized
和wait/notify
API,我们能做的最好的就是遵循以下原则:
生产商:
synchronized (lock) {
while (queue.isFull()) {
lock.wait();
}
queue.put(sth);
lock.notify();
}
消费者:
synchronized (lock) {
while (queue.isEmpty() {
lock.wait();
}
product = queue.take();
lock.notify();
}
这样做的缺点是每次对队列进行更改时都会唤醒生产者和消费者,即使它不可能允许给定线程继续进行(例如,当其他消费者从队列中获取项目时消费者被唤醒)。使用 Lock/Condition API,我们可以实现分离等待消费者和生产者的解决方案,从而减少冗余唤醒和检查:
Lock lock = new ReentrantLock();
Condition hasPlace = lock.newCondition();
Condition hasItems = lock.newCondition();
生产商:
lock.lock();
try {
while (queue.isFull()) {
hasPlace.await();
}
queue.put(sth);
hasItems.signal();
} finally {
lock.unlock();
}
消费者:
lock.lock();
try {
while (queue.isEmpty()) {
hasItems.await();
}
product = queue.take();
hasPlace.signal();
} finally {
lock.unlock();
}
这样,消费者等待生产者生产一些项目(hasItems 条件),并在从队列中删除项目时通知生产者有一个空槽(hasPlace 条件)。这两个条件都与相同的临界区(锁定)相关联,因此我们保留了通常的排除和等待时释放锁的保证,同时获得了分离等待队列的能力。
以前在显式锁之前,我们使用对象wait()
和notify()
方法使线程等待某些事件发生,然后使用触发它们,notify()
并且该对象的互斥锁必须与调用这些方法的线程一起使用。
所以每个锁对象只有一个等待集。等待集是wait()
存储调用对象的线程的集合(不是字面意思)。
但是使用使用单个锁的 Explicit Lock 框架,您可以为与同一锁相关的不同条件创建多个等待集。正如 Javadoc 中的示例也解释了同样的事实。
Multiple Conditions == Multiple Wait sets
final Lock lock = new ReentrantLock(); //Single Lock
final Condition notFull = lock.newCondition(); //Multiple conditions
final Condition notEmpty = lock.newCondition();
因此,在JavaDoc中的 Buffer 示例中,消费者线程将等待 Buffer 为 NOT EMPTY 的条件,而生产者线程将等待条件 NOT FULL。
例如,对于有界数据结构,您可以拥有条件“notEmpty”和“notFull”并等待它们。只是一个例子。看看这里的例子。