AQS系列(二)- ReentrantLock的釋放鎖
前言
在AQS系列(一)中我們一起看了ReentrantLock加鎖的過程,今天我們看釋放鎖,看看老Lea那冷峻的思維是如何在程式碼中筆走龍蛇的。
正文
追蹤unlock方法:
1 public void unlock() { 2 sync.release(1); 3 }
很簡單的一行,呼叫了release方法,引數為1,繼續跟蹤發現不管是公平鎖還是非公平鎖呼叫的都是AbstractQueuedSynchronizer中的release方法:
1 public final boolean release(int arg) { 2 if (tryRelease(arg)) { 3 Node h = head; 4 if (h != null && h.waitStatus != 0) 5 unparkSuccessor(h); 6 return true; 7 } 8 return false; 9 }
此方法看起來簡單,卻暗含殺機。
1、首先看if中的判斷方法tryRelease
1 protected final boolean tryRelease(int releases) { 2 int c = getState() - releases; // 計算出釋放鎖之後的state值 3 if (Thread.currentThread() != getExclusiveOwnerThread()) 4 throw new IllegalMonitorStateException(); 5 boolean free = false; 6 if (c == 0) { // c==0說明要釋放鎖了 7 free = true; 8 setExclusiveOwnerThread(null); //在釋放之前將獨佔執行緒置為空
9 }
10 setState(c); // 將state置為0,此處沒用cas操作,因為沒必要,反正在此之前state都大於0,不會被其他執行緒操作,只有當前執行緒能操作
11 return free;
12 }
此方法的實現邏輯在ReentrantLock類的Sync內部類中,即公平鎖和非公平鎖公用,相信理解起來比較輕鬆。
2、再看裡面的if判斷條件 h != null && h.waitStatus != 0
注意此時h是head,佇列頭。我們先要搞清楚這兩個判斷條件所表示的意思,h!=null說明佇列不是空的,而h.waitStatus != 0又是什麼意思呢?回顧一下上一篇的最後第二個方法 shouldParkAfterFailedAcquire,當時講這個方法時其實描述的不是很清楚,這次重新結合釋放鎖的場景回顧一下。下面先將該方法粘貼出來(註釋中的兩個2表示執行一次這個方法只會走一個2的邏輯):
1 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { 2 int ws = pred.waitStatus; // 1、正常情況進到這裡ws是0,pred可能是head,也可能只是node前面另一個排隊的任務 3 if (ws == Node.SIGNAL) 4 // 3、如果是-1了,就返回true,進入後面park當前執行緒 5 return true; 6 if (ws > 0) { 7 do { 8 // 2、如果是大於0,說明pred執行緒已經被取消,則繼續往前遍歷,直到從後往前找到第一個不大於0的節點,然後互相設定指標 9 node.prev = pred = pred.prev; 10 } while (pred.waitStatus > 0); 11 pred.next = node; 12 } else { 13 // 2、是0的話進這裡,設定成-1,注意是將pred(即當前node的前一個節點)設定成-1。即如果一個節點ws是-1,那麼它後面一定至少還有一個node(就是這個方法中的node) 14 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 15 } 16 return false; 17 }
waitStatus>0只有一種情況-執行緒被取消了(狀態值為1)。當執行緒被取消時就要捨棄掉它,繼續往前遍歷。
回顧完上述的方法,再看h.waitStatus != 0,我們可以知道,waitStatus != 0表示等待後面還有排隊的node(可能是正常狀態也可能是已取消的狀態),這時就要去喚醒下一個正常狀態的執行緒,進入unparkSuccessor方法。
3、unparkSuccessor 方法程式碼
1 private void unparkSuccessor(Node node) { 2 int ws = node.waitStatus; 3 if (ws < 0) 4 compareAndSetWaitStatus(node, ws, 0); 5 6 Node s = node.next; 7 if (s == null || s.waitStatus > 0) { 8 s = null; 9 for (Node t = tail; t != null && t != node; t = t.prev) 10 if (t.waitStatus <= 0) 11 s = t; 12 } 13 if (s != null) 14 LockSupport.unpark(s.thread); 15 }
該方法用於喚醒當前執行緒的下一個有效任務,入參node為head節點。首先如果ws為-1則通過CAS設定為0;然後判斷node的下一個節點是不是空,或者是不是已經被取消(ws大於0表示已經被取消);如果滿足條件,則從後往前遍歷找到從前往後數的第一個ws小於等於0的node節點,喚醒這個節點的執行緒。此處的for迴圈用的比較有意思,用了一種類似於while迴圈的格式來用for迴圈,可見老Lea不拘一格的思維方式。
此處最後一行unpark方法執行之後,就會進入系列(一)中的最後一個方法的第3行程式碼(如下所示),繼續執行下一個執行緒的加鎖過程,進入下一次輪迴。
1 private final boolean parkAndCheckInterrupt() { 2 LockSupport.park(this); 3 return Thread.interrupted(); 4 }
附加:公平鎖與非公平鎖的原始碼理解
在上一篇文章中未講到公平鎖和非公平鎖的區別,在這裡統一進行一下總結:
在釋放鎖的過程中,公平鎖和非公平鎖的處理流程是一樣的,都是從佇列的頭往後遍歷挨個喚醒等待的執行緒。
在加鎖的過程中,有兩個不同的地方。第一個是在lock方法中,公平鎖程式碼:
1 final void lock() { 2 acquire(1); 3 }
非公平鎖程式碼:
1 final void lock() { 2 if (compareAndSetState(0, 1)) 3 setExclusiveOwnerThread(Thread.currentThread()); 4 else 5 acquire(1); 6 }
可以看到非公平鎖直接先用CAS嘗試獲取一下鎖,不用排隊。這就是第一個非公平的地方。
第二個不同的地方,是acquire方法中的tryAcquire方法實現不同,公平鎖的tryAcquire方法:
1 protected final boolean tryAcquire(int acquires) { 2 final Thread current = Thread.currentThread(); 3 int c = getState(); 4 if (c == 0) { 5 if (!hasQueuedPredecessors() && 6 compareAndSetState(0, acquires)) { 7 setExclusiveOwnerThread(current); 8 return true; 9 } 10 } 11 else if (current == getExclusiveOwnerThread()) { 12 int nextc = c + acquires; 13 if (nextc < 0) 14 throw new Error("Maximum lock count exceeded"); 15 setState(nextc); 16 return true; 17 } 18 return false; 19 }
可以看到當c==0時公平鎖會先通過hasQueuedPredecessors方法判斷佇列前面有沒有排隊的。
非公平鎖的實現:
1 protected final boolean tryAcquire(int acquires) { 2 return nonfairTryAcquire(acquires); 3 } 4 5 6 final boolean nonfairTryAcquire(int acquires) { 7 final Thread current = Thread.currentThread(); 8 int c = getState(); 9 if (c == 0) { 10 if (compareAndSetState(0, acquires)) { 11 setExclusiveOwnerThread(current); 12 return true; 13 } 14 } 15 else if (current == getExclusiveOwnerThread()) { 16 int nextc = c + acquires; 17 if (nextc < 0) // overflow 18 throw new Error("Maximum lock count exceeded"); 19 setState(nextc); 20 return true; 21 } 22 return false; 23 }
當c==0時,非公平鎖是直接用CAS嘗試獲取加鎖。這是第二個非公平的地方。
好了,ReentrantLock的加鎖和釋放鎖過程基本就這些了,這週末繼續搞JUC!
&n