在前一章節中,我們簡單分析過aqs中加鎖以及阻塞的流程,這一章我們來分析一下condition條件阻塞工具的實現
## 什么是condition
condition是作為條件阻塞器,通過調用await,signal和signalAll方法來阻塞和喚醒線程,可以橫向對比的是Object對象的wait,notify以及notifyAll方法,值得注意的是,與Object的wait需要跟synchronized結合使用一樣,condition也需要跟鎖結合使用,比如ReenTrantLock中的newCondition方法就是創建一個全新的條件阻塞器,而調用await方法也需要通過lock進行加鎖才可以正常使用.
<!--more-->
## condition.await與Object.wait的區別
* 首先Object相關的阻塞方法都是通過本地方法實現的,而condition的阻塞和喚醒方法都是通過java調用來實現的,其次就是每個Object只能綁定一個阻塞器,即synchronized所綁定的對象,只有通過調用該對象的wait和notify方法才能實現阻塞以及喚醒,并且notify會在調用wait方法的線程中隨機挑選一個喚醒
* 而一個lock可以創建多個condition,例如ReentrantLock中的newCondition方法每次調用都會返回一個新的條件阻塞器,這樣做的好處是,調用condition方法的signal只會喚醒當前condition調用await方法阻塞的線程,利用這種模式可以實現阻塞隊列,如經典的ArrayBlockingQueue就是利用ReenTrantLock創建了兩個condition控制隊列空時的阻塞以及隊列滿時的阻塞
## await方法的實現
老規矩先上源碼
```java
public final void await() throws InterruptedException {
? ? ????????//檢測線程是否中斷
? ? ? ? ? ? if (Thread.interrupted())
? ? ? ? ? ? ? ? throw new InterruptedException();
? ? ????????//添加一個condition的waiter
? ? ? ? ? ? Node node = addConditionWaiter();
? ? ????????//釋放鎖
? ? ? ? ? ? int savedState = fullyRelease(node);
? ? ????????//打斷模式
? ? ? ? ? ? int interruptMode = 0;
? ? ? ? ? ? while (!isOnSyncQueue(node)) {
? ? ????????//進行循環如果當前線程不在同步隊列中,則阻塞線程
? ? ? ? ? ? ? ? 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);
? ? ? ? }
```
await的實現并不難理解,而操作的Node節點與AQS中的node節點是一個對象,通過標記node的waitStatus變量來判斷當前node的狀態,我們再來看看addConditionWaiter的實現
```java
private Node addConditionWaiter() {
? ? ? ? Node t = lastWaiter;
? ? ? ? //如果尾部的等待node被取消了,則遍歷取消所有的被取消的節點
? ? ? ? if (t != null && t.waitStatus != Node.CONDITION) {
? ? ? ? ? ? unlinkCancelledWaiters();
? ? ? ? ? ? t = lastWaiter;
? ? ? ? }
? ? ????//創建一個condition狀態的node節點
? ? ? ? Node node = new Node(Thread.currentThread(), Node.CONDITION);
? ? ????//如果尾結點是空證明是一個空隊列,將頭結點設置為當前節點,否則將當前節點插入當前尾節點的后面
? ? ? ? if (t == null)
? ? ? ? ? ? firstWaiter = node;
? ? ? ? else
? ? ? ? ? ? t.nextWaiter = node;
? ? ? ? lastWaiter = node;
? ? ? ? return node;
}
private void unlinkCancelledWaiters() {
? ? ? ? ? ? Node t = firstWaiter;
? ? ? ? ? ? Node trail = null;
? ? ????????//遍歷取消所有節點狀態不是condition的節點
? ? ? ? ? ? while (t != null) {
? ? ? ? ? ? ? ? Node next = t.nextWaiter;
? ? ? ? ? ? ? ? if (t.waitStatus != Node.CONDITION) {
? ? ? ? ? ? ? ? ? ? t.nextWaiter = null;
? ? ? ? ? ? ? ? ? ? if (trail == null)
? ? ? ? ? ? ? ? ? ? ? ? firstWaiter = next;
? ? ? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? ? ? trail.nextWaiter = next;
? ? ? ? ? ? ? ? ? ? if (next == null)
? ? ? ? ? ? ? ? ? ? ? ? lastWaiter = trail;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? ? ? trail = t;
? ? ? ? ? ? ? ? t = next;
? ? ? ? ? ? }
? ? ? ? }
```
在這個方法中,值得注意的是通過condition維護的隊列,與aqs中排隊的隊列是兩個完全不同的隊列,condition的隊列維護在condition對象中,通過firstWaiter和lastWaiter變量來維護隊列的頭與尾,我們繼續往下看fullyRelease方法
```java
? ? final int fullyRelease(Node node) {
? ? ? ? boolean failed = true;
? ? ? ? try {
? ? ? ? ? ? int savedState = getState();
? ? ? ? ? ? if (release(savedState)) {
? ? ? ? ? ? ? ? failed = false;
? ? ? ? ? ? ? ? return savedState;
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? throw new IllegalMonitorStateException();
? ? ? ? ? ? }
? ? ? ? } finally {
? ? ? ? ? ? if (failed)
? ? ? ? ? ? ? ? node.waitStatus = Node.CANCELLED;
? ? ? ? }
? ? }
```
fullyRelease方法可見是直接釋放當前獨占鎖,在java中目前只有ReenTrantLock以及ReentrantReadWriteLock,實現了newCondition方法,所以共享鎖是不允許condition阻塞的,
繼續向下看isOnSyncQueue方法,顧名思義該方法是判斷當前node是否在同步隊列總
```java
final boolean isOnSyncQueue(Node node) {
? ? ? ? if (node.waitStatus == Node.CONDITION || node.prev == null)
? ? ? ? ? ? return false;
? ? ? ? if (node.next != null) // If has successor, it must be on queue
? ? ? ? ? ? return true;
? ? ? ? return findNodeFromTail(node);
? ? }
```
首先是校驗當前節點的狀態,如果節點狀態還是condition那么一定沒有插入隊列中,而同樣node.prev前面節點如果為空自然是也沒有插入隊列的,后續判斷node.next同樣是判斷后續有沒有等待節點,這里值得注意的是,node.next是同步隊列節點的下一個節點,而condition阻塞隊列的節點為nextWaiter不要弄混了,
如果這兩步判斷沒有成功的話,說明當前節點的prev節點不為空,而next節點為空,而node.prev節點不為空,,但是還沒有在隊列上,因為有可能cas失敗,所以要從尾部遍歷一遍確定在沒在節點中.
如果在同步隊列中則調用acquireQueued嘗試獲取鎖或者排隊,接下來就是判斷是否打斷等流程,后續不在贅述,接下來我們康康signal方法
```java
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;
? ? ? ? ? ? ? ? //將當前節點插入阻塞隊列中
? ? ? ? ? ? } while (!transferForSignal(first) &&
? ? ? ? ? ? ? ? ? ? (first = firstWaiter) != null);
? ? ? ? }
final boolean transferForSignal(Node node) {
? ? ? ? //如果換失敗,則只有可能是被取消了
? ? ? ? if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
? ? ? ? ? ? return false;
????????//將當前線程插入隊列,并返回node節點前面的節點,
? ? ? ? Node p = enq(node);
? ? ? ? int ws = p.waitStatus;
? ? ????//修改前一個節點的狀態為signle以便被喚醒
? ? ? ? if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
? ? ? ? ? ? //如果線程被取消了,或者將waitstatus修改失敗的話,說明當前線程已經被取消了
? ? ? ? ? ? LockSupport.unpark(node.thread);
? ? ? ? return true;
? ? }
```
首先調用signal時一定是要以獨占鎖的模式調用,否則會拋出異常,然后將當前等待節點后移,并且將當前的節點插入阻塞隊列中,不需要喚醒線程因為調用signal時一定是已經被某一線程獲取了鎖,而當調用release時會釋放鎖并且自動調用后續的鎖
那么這里有一個問題就是為什么會在這里調用一次unpark,就算不調用,等到下次喚醒的時候,也會清除掉被取消的節點,這里我查閱資料發現,這次喚醒主要是提升性能,在這里喚醒一次,將前面取消的節點都刪除,以便下次喚醒不需要在刪除節點.這里加不加這個喚醒邏輯上是一樣的
我們再來看看signalAll方法
```java
? ? ? public final void signalAll() {
? ? ? ? ? ? if (!isHeldExclusively())
? ? ? ? ? ? ? ? throw new IllegalMonitorStateException();
? ? ? ? ? ? Node first = firstWaiter;
? ? ? ? ? ? if (first != null)
? ? ? ? ? ? ? ? doSignalAll(first);
? ? ? ? }
? ? ? private void doSignalAll(Node first) {
? ? ? ? ? ? lastWaiter = firstWaiter = null;
? ? ? ? ? ? do {
? ? ? ? ? ? ? ? Node next = first.nextWaiter;
? ? ? ? ? ? ? ? first.nextWaiter = null;
? ? ? ? ? ? ? ? transferForSignal(first);
? ? ? ? ? ? ? ? first = next;
? ? ? ? ? ? } while (first != null);
? ? ? ? }
```
這里實現與signal幾乎相同,只不過一個是將first節點插入隊列,而signalAll方法則是將后續隊列全部插入同步隊列中
到這里我們就已經將condition的實現完全理清了,后續我們也會再分析利用condition來實現的同步阻塞隊列ArrayBlockingQueue