1 Condition接口
任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。
Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现 等待/通知 模式:
Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的。
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
lock.lock();
try {
condition.await();
} finally {
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException {
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}
当调用await()方法后,当前线程会释放锁并在此等待,而其他线程调用Condition对象的signal()方法,通知当前线程后,当前线程才从await()方法返回,并且在返回前已经获取了锁。
常用方法:
2 Condition的实现:
ConditionObject是同步器AbstractQueuedSynchronizer的内部类,因为Condition的操作需要获取相关联的锁,所以作为同步器的内部类也较为合理。每个Condition对象都包含着一个等待队列,该队列是Condition对象实现等待/通知功能的关键。
-
当调用await()方法时,相当于同步队列(当唤醒后,就会在同步队列中竞争锁)的首节点(获取了锁的节点)移动到Condition的等待队列中。
-
调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点), 在唤醒节点之前,会将节点移到同步队列中。
3 Condition与 wait—notify的区别与联系:
联系:
(1)有一组类似的方法:
- 对象监视器: Object.wait()、Object.wait(long timeout)、Object.notify()、Object.notifyAll()。
- Condition对象: Condition.await()、Condition.awaitNanos(long
nanosTimeout)、Condition.signal()、Condition.signalAll()。
(2)都需要和锁进行关联:
- 对象监视器: 需要进入synchronized语句块(进入对象监视器)才能调用对象监视器的方法。
- Condition对象:需要和一个Lock绑定。
区别:
(1)Condition有拓展的方法:
- awaitUninterruptibly():等待时忽略中断
- awaitUntil(Date deadline) throws InterruptedException:等待到特定日期
(2)使用不同:
- 对象监视器: 进入synchronized语句块(进入对象监视器)后调用Object.wait()。
- Condition对象: 需要和一个Lock绑定,并显示的调用lock()获取锁,然后调用 Condition.await()。
(3)等待队列数量:
- 对象监视器: 1个。
- Condition对象: 多个。通过多次调用lock.newCondition()返回多个等待队列。 Condition可以有一个同步队列,多个等待队列。
双队列实例:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
// 生产者方法,往数组里面写数据
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
//数组已满,没有空间时,挂起等待,直到数组不非满(notFull)
notFull.await();
items[putptr] = x;
if (++putptr == items.length)
putptr = 0;
++count;
// 因为放入了一个数据,数组肯定不是空的了
// 此时唤醒等待这notEmpty条件上的线程
notEmpty.signal();
} finally {
lock.unlock();
}
}
// 消费者方法,从数组里面拿数据
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
// 数组是空的,没有数据可拿时,挂起等待,直到数组非空(notEmpty)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length)
takeptr = 0;
--count;
// 因为拿出了一个数据,数组肯定不是满的了
// 此时唤醒等待这notFull条件上的线程
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
(4)Condition可以唤醒特定线程
notify唤醒一个线程是随机的,而condition能做到唤醒特定的一个线程(在等待队列中等待时间最长的节点)。
注意:
同步队列和等待队列:
- 一个线程调用对象的object.wait()方法后会进入等待队列,调用notifyaAll()方法会唤醒所有等待队列中的线程,进入同步队列里竞争锁,调用notify会随机唤醒一个等待队列中的线程进入同步队列,这个是随机的,总之需要区分等待队列与同步队列。只有同步队列里的线程有资格获得锁。