在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的等待隊列中來實現的喚醒操作。