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!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM