文章目錄
- 1 前情回顧 --- 同步方法交替執行時Reentrantlock公平鎖的邏輯
- 2 執行緒t1搶到鎖并且沒釋放的情況
- 2.1 執行緒t2、t3、t4......入隊 --- addWaiter(Node.EXCLUSIVE), arg)方法
- 2.2 執行緒 t2、t3、t4...入隊后 ---》自旋 + park
- 2.2.1 前置知識 --- Node資料結構介紹
- 2.2.2 執行緒 t2、t3、t4...入隊后 ---》自旋 + park的具體流程
- 3 Reentrantlock公平鎖加鎖程序總結
原始碼地址:https://github.com/nieandsun/concurrent-study.git
1 前情回顧 — 同步方法交替執行時Reentrantlock公平鎖的邏輯
前面一篇文章《【并發編程】 — Reentrantlock原始碼決議1:同步方法交替執行的處理邏輯》講過無論是synchronized還是Lock鎖,讓某塊代碼變為同步的本質就是: 當一個執行緒執行該方法后,其他執行緒無法進入該方法,對應于Reentrantlock來說就是讓其他執行緒的lock()方法無法正常回傳,
那篇文章里講過在方法交替執行時,Reentrantlock公平鎖的主要邏輯如下,這里就不再過多敘述,

2 執行緒t1搶到鎖并且沒釋放的情況
2.1 執行緒t2、t3、t4…入隊 — addWaiter(Node.EXCLUSIVE), arg)方法

執行緒2的入隊代碼就這么一點,但是程序挺有意思的,這里總結如下:

當然這只是一種情況,即執行緒t2 成功排在了佇列第2的位置,
其實應該還有一種情況: 即執行緒t2完成了上圖中粉色字體的第(2)步,這時執行緒t3剛好進入addWaiter()方法,發現tail已經不為null了,這時候有可能t3會先排在佇列第2的位置 ,這里就不畫圖了,,,
當然還有可能剛開始時一下有多個執行緒進入enq方法的情況,這里就不展開了,,,
—> 此時應該可以引申出一個思考: Reentrantlock所謂的公平鎖,到底公平在哪???
—> 從這里我們可以看出來,這里所謂的公平并不是你先嘗試獲取鎖,你就一定會最先獲取到鎖,而是你最先進入到了Node佇列,你最先獲取到鎖!!! —這一點我感覺也是非常非常重要,
要非常注意三點(即我在圖中用紅色字體寫的三句話):
- (1)只有被阻塞的執行緒才會進入Node鏈表進行排隊 —> 也就是說獲得鎖的執行緒不會參與排隊
- (2)head節點的Thread永遠為null —》 也就是Node鏈表中的第2個節點是最先進行排隊的執行緒
- (3)當一個執行緒發現前面有Node時,它會直接排在隊尾 —》 也就是說當執行緒3、4,,剛進入addWaiter方法要排隊時,直接就一個個往后排,不用再進enq方法了
本文后面的內容都假設t2的Node排在了佇列第2的位置,
2.2 執行緒 t2、t3、t4…入隊后 —》自旋 + park
其實這兩點應該可以很容易的想到:
- (1)前面入隊只是為了讓執行緒按照先后順序被喚醒,但是要想阻塞執行緒,肯定還是得讓其進行park
- (2)但是因為執行緒的park和unpark肯定要涉及到用戶態和內核態的來回切換,所以在真正的進行park之前先去自旋一下看一看前面的執行緒是不是已經釋放鎖了—> 這樣就有可能不用真正的park了 ,
該邏輯在前面講JDK1.6對synchronized關鍵字的優化《【并發編程】 — synchronized鎖的升級程序 + JDK1.6對synchronized關鍵字的其他優化簡介 》時也講到過 — 但由于沒法除錯,我們其實很難真正去了解它到底是怎么實作的,
接下來我們從原始碼角度看看Doug Lea大神的具體實作方式,
2.2.1 前置知識 — Node資料結構介紹
在介紹Doug Lea大神的具體實作方式之前先來介紹一下Node的具體資料結構,因為只有知道了里面的一個變數waitStatus,才能更好的明白其實作原理,
Node的結構如下:

概括起來可以分為三個部分
-
(1)執行緒的2種等待模式:
- SHARED:表示執行緒以共享的模式等待鎖(如ReadLock)
- EXCLUSIVE:表示執行緒以互斥的模式等待鎖(如ReetrantLock),互斥就是一把鎖只能由一個執行緒持有,不能同時存在多個執行緒使用同一個鎖
-
(2)執行緒在佇列中的狀態列舉:
- CANCELLED:值為1,表示執行緒的獲鎖請求已經“取消”
- SIGNAL:值為-1,表示該執行緒一切都準備好了,就等待鎖空閑出來給我
- CONDITION:值為-2,表示執行緒等待某一個條件(Condition)被滿足 —》 相當于wait、notify/notifyAll的用法
- PROPAGATE:值為-3,當執行緒處在“SHARED”模式時,該欄位才會被使用上
- 初始化Node物件時,默認為0
-
(3)成員變數:
- waitStatus:該int變數表示執行緒在佇列中的狀態,其值就是上述提到的CANCELLED、SIGNAL、CONDITION、PROPAGATE
- prev:該變數型別為Node物件,表示該節點的前一個Node節點(前驅)
- next:該變數型別為Node物件,表示該節點的后一個Node節點(后繼)
- thread:該變數型別為Thread物件,表示該節點的代表的執行緒
- nextWaiter:該變數型別為Node物件,表示等待condition條件的Node節點
注意: 只有未獲得鎖,或者說被阻塞的方法才會進入Node鏈表中 , 已經獲得鎖的執行緒肯定不會進入Node鏈表,
知道了Node的資料結構之后,還必須知道Reentrantlock的公平鎖和和非公平鎖都是獨占鎖 —》因此在不涉及到執行緒互動的情況下,公平鎖的waitStatus只可能有三個值,即1 --- CANCELLED、-1 --- SIGNAL 和 0 ---初始化Node時的默認值, —> 這一點務必要記住!
2.2.2 執行緒 t2、t3、t4…入隊后 —》自旋 + park的具體流程
具體流程總結如下:

3 Reentrantlock公平鎖加鎖程序總結
自認為Reentrantlock公平鎖的加鎖程序被我這么一總結,還挺好理解的(☆_☆) ,為了更深刻一些,這里再做一個總結:

如果讓我再用幾句更簡單的話來總結的話,我會這樣進行總結:
-
(1)排隊時,隊首肯定是一個Thread為null的Node,其他執行緒的Node每次入隊時都排在隊尾(并維護雙向鏈表關系)
-
(2)自旋 + park的程序 —》 如上圖,哈哈哈!!!
end!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/73531.html
標籤:其他
上一篇:線性相關與線性無關
下一篇:資料中心視頻下載
