
前兩節你應該掌握了ReentrantLock加鎖成功和加鎖失敗入隊的核心邏輯,是如何通過AQS中的3個組件做到的,今天來我們看下:
ReentrantLock中,當執行緒釋放鎖時的邏輯
釋放鎖的程序及原始碼剖析
釋放鎖的程序及原始碼剖析
目前經過執行緒1、執行緒2使用ReentrantLock.lock()后的結果如下:

執行緒2入隊等待,執行緒1持有鎖,state=1,owner是執行緒1,佇列中元素是執行緒2,佇列是由Node節點組成的雙向鏈表,頭結點為空,佇列具體情況如下:

此時假設執行緒1呼叫了unlock()方法,進行釋放鎖,會做哪些事情呢?
首先我們來看一下釋放的代碼:
public void unlock() {
sync.release(1);
}
非常簡單,還是使用Sync組件(AQS)來釋放鎖,release()方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
核心分為了2步:
1) tryRelease方法,釋放state和owner變數
2) unparkSuccessor方法,喚醒佇列元素
分別來看一下,首先釋放變數tryRelease方法:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
這個方法邏輯很清晰,當前執行緒是執行緒1,state=1,入參releases是1,表示釋放一次鎖,很明顯,state減1后變為0,會將owner也置為空,表示當前沒有執行緒持有鎖了,之后設定state為0結束,
整個程序如下圖所示:

修改了owner和state兩個組件的值后,第二步就是喚醒佇列等待的執行緒了,
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
首先釋放成功鎖后,使用了h指標指向了當前佇列的頭部,判斷一下佇列中是否有等待的元素,注意對頭元素waitStatus不能是0,如果是0,說明佇列只有一個空節點,佇列中沒有等待元素,因為入隊元素后會將頭結點的waitStatus改成-1,SIGNAL,
接著進入了unpartSuccessor方法,從名字看就是恢復在h節點之后掛起的執行緒,(PS:JDK原始碼里面總會出現一些語意相近的詞語,比如first-last,head-tail,successor- predecessor,prev-next,其實都是表示前后的意思,是等價的,這點大家要清楚,)
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
node就是入參h,head節點,首先把head節點waitStatus從-1改為0,
接著s=node.next表示的就是執行緒2排隊的Node,s不為空,執行了LockSupport.unpark(s.thread); 將執行緒2進行了喚醒,
最后整個方法回傳,release回傳true表示釋放鎖成功,如下圖所示:

此時之前被掛起的執行緒2,還記得嗎?就是執行緒2曾經執行了LockSupport.park(t)的操作,執行緒被掛起,代碼會hang在那一行等待,
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
執行緒2就會接著執行,只要執行緒2沒被打斷過,回傳的肯定是false,以下的if條件不會成立,
if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())
接著回到for回圈,重新進行tryAcquire操作,執行緒2再次嘗試獲取鎖,此時假設沒有別的執行緒過來加鎖,執行緒2自然就會獲取鎖,設定state=1,owner=執行緒2了,
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
上面整個釋放鎖的程序可以總結如下圖:

思考&長圖總結ReentrantLock
思考&長圖總結ReentrantLock
我們分析ReentrantLock的加鎖和釋放鎖,今天最后小結的一個思想其實就是:由脈絡到細節,再從細節脈絡,
另外 為了讓大家更好的掌握ReentrantLock的AQS原理,這里給大家總結了一下,,
首先可以把原理用一句話簡單的講:假設有兩個執行緒同時進行獲取鎖的操作,只能有一個獲取成功,另一個會進入一個佇列等待,底層是結合了state、owner,Node佇列AQS的3個組件配合起來做到的,
ReentrantLock核心加鎖和釋放流程可以總結為如下一張長圖:

本文由博客群發一文多發等運營工具平臺 OpenWrite 發布
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/341690.html
標籤:Java
