AQS(AbstractQueuedSynchronizer)源代码分析(四)——Condition锁

     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毕竟不是单独使用的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值