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...