JUC併發基石之AQS原始碼解析 獨佔鎖的釋放

2021-10-07 08:06:35 字數 3052 閱讀 5133

juc併發基石之aqs原始碼解析–獨佔鎖的獲取

public final boolean release

(int arg)

return

false

;}

獨佔鎖的涉及到兩個函式的呼叫:

1.tryrelease(arg) ,該方法由aqs的子類來實現釋放鎖的具體邏輯

2.unparksuccessor(h) ,喚醒後繼執行緒

我們以reentrantlock為例看看tryrelease(arg)的實現:

tryrelease(arg)

protected final boolean tryrelease

(int releases)

// 設定狀態

setstate

(c);

return free;

}

我們可以看到,釋放鎖就是對state進行操作,不過因為是可重入鎖,所以只有當state=0的時候,才是真正的釋放鎖。

假如真正釋放鎖之後,我們來看看喚醒執行緒的邏輯:

unparksuccessor()

private

void

unparksuccessor

(node node)

// 如果找到了還在等待鎖的節點,則喚醒它

if(s !=

null

) locksupport.

unpark

(s.thread)

;}

我們可以看到後繼節點不為空,而且waitstatus <= 0,即沒有取消獲取鎖,那麼就去喚醒後繼節點。

如果後繼節點取消獲取鎖,那麼從尾節點開始找,排在最前面的等待獲取鎖的節點

這裡有乙個的問題就是, 為什麼要從尾節點開始逆向查詢, 而不是直接從head節點往後正向查詢, 這樣不是更快麼?

其實是跟入隊的操作有關:

private

node

addwaiter

(node mode)

}// 執行到這裡, 只有兩種情況:

// 1. 隊列為空

// 2. 其他執行緒在當前執行緒入隊的過程中率先入隊,導致尾節點的值改變,所以cas操作失敗

enq(node)

;return node;

}

因為這個阻塞佇列是雙向鍊錶,所以入隊的節點前進行第一步和第二步操作,把尾節點設為自己的pre節點,並且cas設定自己為尾節點,設定成功了,再把之前尾節點的next節點設為當前節點。

所以如果我們unparksuccessor從頭開始遍歷,如果cas設定尾節點成功,但是pred.next的值還沒有被設定成node,從前往後遍歷的話,有可能遍歷不到剛加入的尾節點的。

如果從後往前遍歷的話,因為尾節點此時已經設定完成,node.prev = pred操作也被執行過了,那麼新加的尾節點就可以遍歷到了,並且可以通過它一直往前找。

如果找到了還在等待鎖的節點,則喚醒它,也就是呼叫locksupport.unpark(s.thread)。

private final boolean parkandcheckinterrupt()

喚醒的執行緒會在acquirequeued()方法裡面繼續嘗試獲取鎖:

final boolean acquirequeued

(final node node,

int arg)

// 在這裡被喚醒之後,又會在for迴圈裡面繼續嘗試獲取鎖if(

shouldparkafte***iledacquire

(p, node)

&&parkandcheckinterrupt()

) interrupted =

true;}

}finally

}

這樣,釋放鎖就和獲取鎖結合在一起了。

不過這裡我們再關注一點,也就是中斷。

獲得鎖的執行緒在釋放鎖的時候,會呼叫release()方法,這個方法會呼叫locksupport.unpark()喚醒掛起執行緒;被喚醒後,在acquirequeued()迴圈嘗試獲取鎖。

當前執行緒被中斷了,那麼也會喚醒,喚醒後呼叫thread.interrupted()方法返回true,同時中斷標誌位會被清空,不過在外層會把interrupted 設為 true。

我們可以看到,如果執行緒是被中斷喚醒的,那麼呼叫thread.interrupted()會返回true,同時中斷標誌位會被清空。因為parkandcheckinterrupt()返回true,所以interrupted 設定為true。

if

(shouldparkafte***iledacquire

(p, node)

&&parkandcheckinterrupt()

) interrupted =

true

;

之後如果獲取鎖,那麼acquirequeued()返回的是interrupted,也就是true,我們再回到acquirequeued()方法呼叫的地方:

public final void

acquire

(int arg)

如果acquirequeued的返回值為true, 我們將執行 selfinterrupt():

static

void

selfinterrupt()

它其實就是中斷了一下執行緒。

其實做了這麼多,都是因為獲取鎖的時候是不響應中斷的!

所以當在locksupport.park(this)處被喚醒,可能是因為當前執行緒在等待中被中斷了,因此我們通過thread.interrupted()方法檢查了當前執行緒的中斷標誌,並將它記錄下來,當它搶到鎖了,返回acquire方法後,如果發現當前執行緒曾經被中斷過,就再中斷自己一次,將這個中斷補上。

AQS之await和signal原始碼解析

上篇的文章中我們介紹了aqs原始碼中lock方法和unlock方法,這兩個方法主要是用來解決併發中互斥的問題,這篇文章我們主要介紹aqs中用來解決執行緒同步問題的await方法 signal方法和signalall方法,這幾個方法主要對應的是synchronized中的wait方法 notify方法...

java併發之AQS共享模式原始碼解讀

共享鎖的乙個實現類就是訊號量semaphore,semaphores一般用於對某種訪問資源的限制,乙個訊號量相當於持有一些許可 permits 執行緒可以呼叫semaphore物件的acquire 方法獲取乙個許可,呼叫release 來歸還乙個許可。同樣semaphore有公平和非公平兩種模式。預...

JDk原始碼解析之四 Vector原始碼解析

具體的三個屬性 解釋看圖中注釋。vector沒有採取arraylist臨界值擴容的辦法,而是每次不夠的時候,直接根據capacity的值來增加。具體怎麼增加後面會說。vector的構造方法如下。簡單粗暴,如果呼叫無參建構函式,直接就將初始容量設定成了10,最終在右側的構造方法裡,將陣列的長度設定為1...