在Java concurrent包源码走读(二)我们知道AQS中有个条件队列,但是具体它的作用是干什么、它和同步队列有个关系,接下来这篇我们来了解AQS中的条件队列。首先我们先看一下和条件队列关联的ReentrantLock类。
ReentrantLock
类图
从类图中我们可以看到此类实现Lock,同时有个抽象类Sync,Sync这个类是继承AbstractQueuedSynchronizer,同时它也两个子类,FairSync和NonfairSync。由此可见ReentrantLock它可以公平的获取锁也可以非公平方式获取锁。我们通过源码还可以看出ReentrantLock特点:
互斥锁
支持公平和非公平获取锁,默认是非公平
可重入锁
支持条件变量(实现Lock接口)
对于ReentrantLock获取释放锁的源码我们就再分析,感兴趣的同学可以走读,主要看tryAcquire和tryRelease方法。走读的时候可以带着下面的两个问题?
ReentrantLock如何实现可重入?
ReentrantLock的Lock为何要需要try catch,并且lock需要在try的外面?
条件队列
我们主通过await和signal方法分析同步队列和条件队列的交互。
await()方法
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//将节点放入等待队列
Node node = addConditionWaiter();
//释放节点占的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//轮询判断节点是否在AQS队列
while (!isOnSyncQueue(node)) {
//如果在则阻塞节点对应的线程
//它是何时加入到AQS队列中呢?signal()
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//唤醒后继续竞争锁,失败后继续阻塞
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
signal()方法
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
//唤醒等待队列第一个节点,注意只是唤醒,竞争到锁的看AQS队列
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
通过上面源码的分析,我们知道AQS自己维护的队列是当前等待资源的队列,AQS会在资源被释放后,依次唤醒队列中从前到后的所有节点,使他们对应的线程恢复执行。直到队列为空。而条件队列维护一个等待signal信号的队列,两个队列的作用是不同,事实上,每个线程也仅仅会同时存在以上两个队列中的一个,流程是这样的:
线程1调用reentrantLock.lock时,线程被加入到AQS的等待队列中。
线程1调用await方法被调用时,该线程从AQS中移除,对应操作是锁的释放。
接着马上被加入到Condition的等待队列中,意味着该线程需要signal信号。
线程2因为线程1释放锁的关系,被唤醒,并判断可以获取锁,于是线程2获取锁,并被加入到AQS的等待队列中。
线程2调用signal方法,这个时候Condition的等待队列中只有线程1一个节点,于是它被取出来,并被加入到AQS的等待队列中。注意,这个时候线程1并没有被唤醒
signal方法执行完毕,线程2调用reentrantLock.unLock()方法,释放锁。这个时候因为AQS中只有线程1,于是,AQS释放锁后按从头到尾的顺序唤醒线程时,线程1被唤醒,于是线程1回复执行。
直到释放所整个过程执行完毕。
可以看到,整个协作过程是靠结点在AQS的等待队列和条件队列中来回移动实现的,条件队列维护了一个等待信号的节点,并在适时的时候将结点加入到AQS的等待队列中来实现的唤醒操作。