参考文献:http://www.jianshu.com/p/6b5aa7b7684c、http://ifeve.com/understand-condition/、http://www.cnblogs.com/cm4j/p/juc_condition.html
Condition的使用
通常我们会这样来new一个Condition
ReentrantLock lock = new ReentrantLock(); //默认的构造方法创建的是非公平锁
Condition isEmpty = lock.newCondition();
Condition isFull = lock.newCondition();
condition中的方法很像普通对象锁的wait()和notify(),而一个lock理论上可以创建n个condition,这也是JUC包带来的一大改变
Condition结构
当我们调用ReentrantLock里的condition方法时,他返回的是一个ConditionObject对象
final ConditionObject newCondition() {
return new ConditionObject();
}
该对象是AQS类的一个内部类,以下是部分类对象,可见Condition内部也维护了一个队列
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;
await方法
调用await,当前线程在此Condition上等待,同时将此线程实例化成一个Node,加入等待队列中
public final void await() throws InterruptedException {
//判断是否中断
if (Thread.interrupted())
throw new InterruptedException();
//在该Condition维护的等待队列中增加节点
Node node = addConditionWaiter();
//将当前的线程占用的Lock释放,通过修改Lock的State实现
int savedState = fullyRelease(node);
int interruptMode = 0;
//释放完毕后,遍历AQS的队列,看当前节点是否在队列中,不在,说明它还没有竞争锁的资格,所以继续将自己沉睡。
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//这个时候线程已经被signal()或者signalAll()操作给唤醒了,退出了上面的while循环
// 自旋等待尝试再次获取锁,调用acquireQueued方法
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
首先是检查中断。如果中断,那么抛出中断异常。
如果没有中断,调用addConditionWaiter():
在Condition的队列(是一个双向链表)中增加一个Node节点,代表当前Wait的线程,等待Signal信号唤醒。
此处Node的首尾节点都没有用Volatile修饰,是因为到await方法的线程都是获取锁的,利用锁已经能保证了Node的可见性了。也就是说,此方法本身就是线程安全的,这里就没必要利用Volatile保证可见性。
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
////遍历整个双向链表,清除已经取消的节点
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
接下来是判断当前的节点是不是在同步队列中,这里可能会有一点疑问,就是为什么是判断同步队列而不是等待队列呢?
其实在后面signal的时候会有一个将node转入到同步队列的过程,从而使线程重新加入到锁竞争中。这里判断是否在同步队列中,其实就是判断是不是已经signal:
final boolean isOnSyncQueue(Node node) {
//此处第一个判断当前Node的状态,如果是CONDITION,表明是正常的没有被唤醒的节点,返回false 表名当前的节点还没进入到Syn队列
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 说明当前Node的状态不是CONDITION,同时它既有prev节点也有next节点,那么说明它在AQS队列里
if (node.next != null)
return true;
//遍历整个Node的Syn队列,如果存在返回true,这里有一个Wait队列到Syn队列的转化过程
return findNodeFromTail(node);
}
如果之前返回了true,那么表示已经在队列中,那么继续争抢锁,这种情况主要发生在已经释放锁,但是还没有park线程的时候,线程就被singal。如果返回false,证明没有singal,所以要park当前的线程。
signal方法
public final void signal() {
//检查当前线程是不是Lock占用的线程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
//取出Condition队列中第一个Node
if (first != null)
//唤醒
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)//修改头结点,完成旧头结点的移出工作
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && //将老的头结点,加入到AQS的等待队列中
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* 如果无法改变状态,说明该节点已经被取消了;
* 如果cancelled,返回false,上层的while循环会继续唤醒下一个节点
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node); // 加入AQS队列
int ws = p.waitStatus;
//如果该结点的状态为cancel或者修改waitStatus失败,则直接唤醒。
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
可以看到,正常情况 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL) 这个判断是不会为true的,所以,不会在这个时候唤醒该线程。
上面的代码很容易看出来,signal就是唤醒Condition队列中的第一个非CANCELLED节点线程。
signalAll和signal方法类似,主要的不同在于它不是调用doSignal方法,而是调用doSignalAll方法:
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
这个方法就相当于把Condition队列中的所有Node全部取出插入到等待队列中去。
总结
AQS自己维护的队列是当前等待资源的队列,AQS会在资源被释放后,依次唤醒队列中从前到后的所有节点,使他们对应的线程恢复执行。直到队列为空。
而Condition自己也维护了一个队列,该队列的作用是维护一个等待signal信号的队列,两个队列的作用是不同,事实上,每个线程也仅仅会同时存在以上两个队列中的一个,流程是这样的:
1.线程1调用reentrantLock.lock时,线程被加入到AQS的等待队列中。
2.线程1调用await方法被调用时,该线程从AQS中移除,对应操作是锁的释放。
3.接着马上被加入到Condition的等待队列中,意味着该线程需要signal信号。
4.线程2,因为线程1释放锁的关系,被唤醒,并判断可以获取锁,于是线程2获取锁
5.线程2调用signal方法,这个时候Condition的等待队列中只有线程1一个节点,于是它被取出来,并被加入到AQS的等待队列中。 注意,这个时候,线程1 并没有被唤醒。
6.signal方法执行完毕,线程2调用reentrantLock.unLock()方法,释放锁。这个时候因为AQS中只有线程1,于是,AQS释放锁后按从头到尾的顺序唤醒线程时,线程1被唤醒,于是线程1恢复执行。
7.直到释放所整个过程执行完毕。
可以看到,整个协作过程是靠结点在AQS的等待队列和Condition的等待队列中来回移动实现的,Condition作为一个条件类,很好的自己维护了一个等待信号的队列,并在适时的时候将结点加入到AQS的等待队列中来实现的唤醒操作。
注意
上面的将线程一从AQS中移除并加入到Condition队列中,移除对应的操作是AQS中head的重置。
我们知道只有获得锁的线程才能调用await或signal方法,所以当调用await方法时,意味着该线程所对应的Node是AQS的头结点,当它放弃锁时,唤醒了它的后继节点,后继节点得锁后(在acquireQueued方法中)设置自身为新的head,从而线程一也相应地在AQS中被移除。