Condition锁是一种带条件的锁,当条件不满足时,它会挂起当前的线程,等待条件满足。当条件满足时候,会唤起当前的线程,继续执行后续的操作。其运行原理图如下:
在上图中,虚线框内是AQS的非共享模式的运行原理,具体的运行过程已经在AQS(AbstractQueuedSynchronizer)源代码分析(二)文章中讲解过,这里不再赘述。我们这里主要关注Condition队列部分。
先看一段代码,这段代码演示了通常情况下,我们使用condition锁的情景。代码如下:
public class PrintQueue {
private final Lock lock = new ReentrantLock();
private LinkedBlockingDeque<String> queue = new LinkedBlockingDeque<>();
private final Condition waiter = lock.newCondition();
public void printJob(Object document){
lock.lock(); //获取lock
try {
while(queue.size() <= 0){ //队列中没有元素,所以不能继续执行
waiter.await(); //等待条件满,即等待queue队列中有元素
}
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
从代码中,可以看到:当调用await()方法时触发了condition锁。所以,我们的分析重点应该放在await()方法上。
从上面的图片中,我们可以发现:当condition条件如果满足,就会直接进入绿色块,也就是可以继续执行;当condition条件不满足时,则会将当前线程追加到condition队列,同时,也会挂起当前的线程。对于继续执行没有什么好解释的,就是代码逐行继续执行。接着来看一下源代码上是如何将一个线程挂起,并追加到Condition队列上的。await()方法源代码如下:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); //追加节点
//fullyRelease会释放已经获得的lock,因为当前线程需要等待条件,
//不能继续执行,所以需要将其获得的lock释放掉
//就像排队候车一样,虽然当前你可以上车,但是你还要等人
//此时你就会放弃自己的上车机会,让给别人
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断当前节点是否已经在SyncQueue队列上,这里的
//SyncQueue队列也就是没有condition队列。
//因为当Condition条件满足时,会将Condition队列的node
//追加的SyncQueue队列中。所以会有这个循环判断
while (!isOnSyncQueue(node)) {
//如果没有在SyncQueue队列中,则挂起线程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//当Condition队列中的node已经被追加到syncQueue队列中,则会执行
//acquireQueued方法,这个方法将会重新执行AQS的获取(acquire)操作,后面的执行过程
//就与AQS的正常运行时一样的
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
上面的源代码已经标明了追加节点和挂起线程两个重要的步骤。所以接下来应该重点分析一下追加节点的addConditionWaiter()方法和isOnSyncQueue()。因为挂起线程只是一个动作,且是由虚拟机帮助我们完成的,所以不必过于深究。这里我们只需要分析挂起线程的isOnSyncQueue()判断即可。addConditionWaiter()源代码如下:
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
//这里是防止lastWaiter被取消,如果lastWaiter取消,则从队列末尾依次向前查找一个未取消的node
//将查找到的node最为尾节点
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION); //创建新的节点,状态是Condition
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node; //当队列的最后一个node的nextWaiter指向新建的Node
lastWaiter = node; //标记队列的最后一个节点为新建的Node
return node;
}
isOnSyncQueue()源代码如下:
final boolean isOnSyncQueue(Node node) {
/** 如果当前node是Condition状态,或者prev为null,则表面该node还在
condition队列。
因为,prev是SyncQueue同步队列中用于指向前一个
执行Node的引用,在Condition队列中是不会有这个引用的,所以为null
下面一个if判断中的next引用也是同理,next是SyncQueue同步队列所使用的
用于标记当前node的下一个node.
Condition队列中使用的nextWaiter来标记下一个执行节点
*/
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//如果有nextNode,表明一定在syncQueue同步队列中
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node); //如果上面两个判断都无法确定,则在整个队列中进行查找
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
根据上面的分析,还存在一个疑问:node队列是在哪里,以及什么时候被放入到SyncQueue同步队列中的?这个步骤是在Condition的唤醒方法中完成的,也就是当Condition要求的条件满足时会执行它的唤醒方法,即signal()或者signalAll()
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null) //头结点不为null,说明Condition队列中有Node
doSignal(first); //执行唤醒
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null) //判断是否还有下一个节点
lastWaiter = null;
first.nextWaiter = null;
//这里用了一个快速与,并且融合了连个操作。
//如果transferForSignal返回false,则会将firstWaiter赋值给first
//此时的firstWaiter已经是first.nextWaiter了,因为上面的if判断中已经
//进行了赋值操作。通过这种方式达到遍历队列的目的
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
根据上面的分析,Signal方法的执行中心是transferForSignal()方法。transferForSignal()方法就是讲condition队列的node添加到SyncQueue队列的方法。源代码如下:
final boolean transferForSignal(Node node) {
//如果状态不能修改为0,表明节点已经被取消了。因为除了signal方法
//是不会有其他方法修改Condition队列的node的状态的。只有可能是追加节点时失败,
//或者当前线程被取消
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//enq方法将node节点追加到SyncQueue队列上。
//返回node的prevNode,即SyncQueue队列中的当前node的前一个node
Node p = enq(node);
int ws = p.waitStatus;
//prevNode的状态大于0,说明该节点已经被取消
//如果prevNode的状态小于0,则将prevNode的状态修改SIGNAL
//SIGNAL状态表示prevNode有后续节点需要唤醒。(Node的状态在前一篇文章中已经解释过)
//如果修改prevNode的状态成功,则不做任何处理,直接返回。因为node的Thread由prevNode来
//唤醒,这里就不需要再次唤醒了
//如果修改失败,则唤醒当前node的线程,以确保线程可以被唤醒
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
到这里,整个Condition多的获取和释放已经解释完了。要很好的理解这个过程的话,需要结合AQS的非共享模式的运行方式进行理解。因为Condition毕竟不是单独使用的。