任意一个java.lang.Object都有一组监视器方法wait()、notify()、notifyAll(),通过这些方法和synchronized关键字配合使用可以实现等待/通知机制。通过这种机制可以轻而易举的实现消费者、生产者模式。具体实现搜索一下有很多
Condition是在java1.5中引入的,将这些对象与任意 Lock 实现组合使用,可以为每个对象提供多个等待集合。相比Object实现的监视器方法,Condition接口的监视器方法具有一些Object所没有的特性:
如下代码是消费者生产者模式,lock创建了两个Condition对象 notFull,notEmpty。
生产方法put先加锁,然后循环等待notFull Condition。当items 数组未满时,插入数据,最后唤醒在notEmpty上等待的线程,释放锁。
消费方法take先加锁,然后循环等待notEmpty Condition,当items数组不为空时,获取数据,最后唤醒在notFull上等待的线程,释放锁。
await()方法会释放锁资源直到被唤醒重新获取被释放的资源,所以put get方法不会死锁。
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.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
我们以ReentrantLock讲解Condition
ReentrantLock.newCondition()方法使用的是内部类Sync的newCondition()
public Condition newCondition() {
return sync.newCondition();
}
Sync的newCondition方法则直接new一个AQS的内部类ConditionObject返回
final ConditionObject newCondition() {
return new ConditionObject();
}
ConditionObject实现了Condition接口,和AQS一样,内部维护一个FIFO队列保存等待线程。并且没有state属性。
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
}
Node类包含三个指针,prev next组成AQS双向 FIFO队列,nextWaiter组成Condition 单向的FIFO队列
await大致包含四个步骤
1.获取当前线程占有资源数
2.释放当前所有资源
3.等待直至被唤醒
4.重新获取步骤1保存的资源数
awaitUninterruptibly() await() 区别在于awaitUninterruptibly不管线程是否被中断,await方法如果线程被中断会抛出InterruptedException
awaitUninterruptibly更简单,我们来看awaitUninterruptibly源码
public final void awaitUninterruptibly() {
Node node = addConditionWaiter();//添加一个Condition 等待节点
int savedState = fullyRelease(node);//释放所有资源
boolean interrupted = false;
while (!isOnSyncQueue(node)) {//如果节点没被转到AQS队列,继续休眠
LockSupport.park(this);
if (Thread.interrupted())
interrupted = true;
}
//重新获取资源
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}
addConditionWaiter()和fullyRelease(node)比较简单
我们来看下isOnSyncQueue方法
/**
* 如果一个节点最初在Conditon等待队列上,现在在AQS的等待队列上则返回true
*/
final boolean isOnSyncQueue(Node node) {
//当前节点为Node.CONDITION状态,说明还未被唤醒
//node.prev == null说明已经在AQS队列头,已经持有资源
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//如果node.next不为空,说明已经在AQS队列上
if (node.next != null) // If has successor, it must be on queue
return true;
//从AQS FIFO队列尾部开始匹配当前节点是否在AQS队列中
return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
Node t = tail;//获取AQS尾节点
for (;;) {//遍历整个AQS FIFO队列
if (t == node) //当前节点在AQS 队列中,返回true
return true;
if (t == null) //当前节点为空,说明已经遍历完了,返回false
return false;
t = t.prev;//获取前一节点
}
}
至此await主要流程源码就已经讲解完了。下面来看下signal怎么处理的
public final void signal() {
if (!isHeldExclusively())//当前线程不是锁持有者直接抛出异常
throw new IllegalMonitorStateException();
Node first = firstWaiter;//获取condition 头节点
if (first != null)
doSignal(first);//唤醒头节点
}
private void doSignal(Node first) {
do {
//设置Condition队列头节点为下一节点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;//如果后续没有节点,设置尾节点为null
//1.当前节点加入到AQS队列中
//2.如果1成功,跳出循环
//3.如果1失败。获取Condition 队列头节点,如果头节点为空跳出循环,不为空继续循环
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* 转变当前节点状态从Node.CONDITION到0,如果失败说明已经被其他线程唤醒
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将node添加到AQS队列尾部,并获取前一节点
Node p = enq(node);
int ws = p.waitStatus;
//如果前一节点waitStatus>0即Node.CANCELLED状态,唤醒node对应的线程
//如果前一节点waitStatus<=0,设置前一节点为Node.SIGNAL状态,如果设置失败,说明前一节点状态 已经改变,唤醒node对应线程,尝试获取锁
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
Condition源码的分析到此就结束了,大体其实还是跟AQS一样,使用一个FIFO队列维持等待顺序。只是一个锁可以new多个Condition,解决了Object.wait只有一个等待队列的问题。