Java并發(fā)編程-淺談ReentranLock
1. 產(chǎn)生背景
在Java中已經(jīng)有內(nèi)置鎖synchronized的情況下,為什么還需要引入ReentranLock呢?
synchronized是基于 JVM內(nèi)置鎖實現(xiàn),通過內(nèi)部對象Monitor(監(jiān)視器鎖)實現(xiàn),基于進(jìn)入與退出Monitor對象實現(xiàn)方法與代碼塊同步,監(jiān)視器鎖的實現(xiàn)依賴底層操作系統(tǒng)的Mutex lock(互斥鎖)實現(xiàn),被阻塞的線程會被掛起、等待重新調(diào)度,會導(dǎo)致“用戶態(tài)和內(nèi)核態(tài)”兩個態(tài)之間來回切換,對性能有較大影響。
學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Qb4y1D75J
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1qo4y1f7Uw
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1s64y1i77s
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1g84y1F7vS
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1a54y1b7hh
基于這樣一個背景,Doug Lea在JDK1.5中提供了API層面的互斥鎖ReentranLock,實現(xiàn)了可重入、可中斷、公平鎖等特性。在synchronized優(yōu)化之前,synchronized的性能比起ReentranLock還是有差距的。
2. 簡單使用

3. 源碼分析
3.1 ReentrantLock結(jié)構(gòu)
首先,RentrantLock對象結(jié)構(gòu),有個大致的了解。
學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Qb4y1D75J
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1qo4y1f7Uw
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1s64y1i77s
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1g84y1F7vS
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1a54y1b7hh

3.2 加鎖 lock.lock()
其實,ReentrantLock中的方法都是由成員對象sync完成的。
所以,我們直接進(jìn)入 NonfairSync.lock()
學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Qb4y1D75J
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1qo4y1f7Uw
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1s64y1i77s
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1g84y1F7vS
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1a54y1b7hh

接下來,分析 acquire()中的三個重點方法:
tryAcquire():嘗試獲取鎖,由子類重寫(這里實現(xiàn)了可重入、公平鎖特性);
addWaiter():AQS中維護(hù)了一個鏈表,將當(dāng)前線程包裝成一個Node節(jié)點,入隊;
acquireQueued():以獨占不可中斷模式獲取已經(jīng)在隊列中的線程。
3.2.1 tryAcquire

當(dāng)前方法無論是獲取到了鎖,還是重復(fù)加鎖,都是返回true。上面的 acquire()就會直接結(jié)束,不會繼續(xù)執(zhí)行后續(xù)的入隊操作。
學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Qb4y1D75J
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1qo4y1f7Uw
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1s64y1i77s
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1g84y1F7vS
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1a54y1b7hh
非公平鎖的特性就是在這個方法中體現(xiàn)出來的(其實lock方法中也是直接獲取鎖,更直接)。
3.2.2 addWaiter
注意,當(dāng)前方法調(diào)用時,傳入了一個參數(shù):Node.EXCLUSIVE(獨占模式)。這個參數(shù)實際上是起到了一個標(biāo)識的作用,有兩種類型:獨占、共享。

addWaiter()的職責(zé)很簡單,就是將當(dāng)前線程包裝成一個Node節(jié)點,然后設(shè)置為隊尾(必要時初始化隊列),通過死循環(huán)的形式保證一定入隊成功。
學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Qb4y1D75J
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1qo4y1f7Uw
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1s64y1i77s
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1g84y1F7vS
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1a54y1b7hh
下面,大致了解一下Node的結(jié)構(gòu)(AbstractQueuedSynchronizer的內(nèi)部類)

3.2.3 acquireQueued

代碼執(zhí)行到這里,說明節(jié)點(當(dāng)前線程)已經(jīng)成功入隊。如果當(dāng)前節(jié)點就是第一位候選者,就會嘗試去獲取鎖。但是鎖有可能還沒有釋放掉(state != 0),獲取鎖失敗,就會阻塞當(dāng)前線程。
學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Qb4y1D75J
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1qo4y1f7Uw
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1s64y1i77s
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1g84y1F7vS
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1a54y1b7hh

相信大家也看到了,這個方法中有段代碼的寫法太(bi)過(jiao)精(e)簡(xin)。

其中,紅色方塊的pred和N1狀態(tài)都是已取消的(狀態(tài)值為1)。
首先,執(zhí)行if判斷時,發(fā)現(xiàn)pred的等候狀態(tài)是已取消的,執(zhí)行do代碼塊,將pred指向N1節(jié)點。執(zhí)行while判斷,發(fā)現(xiàn)N1節(jié)點的狀態(tài)也是已取消的,再次執(zhí)行do代碼塊,將pred指向N2節(jié)點。執(zhí)行while判斷,N2節(jié)點狀態(tài)并不是已取消,循環(huán)結(jié)束。而在循環(huán)結(jié)束之前node的前驅(qū)指針已經(jīng)指向了N2,循環(huán)結(jié)束后再將N2的后驅(qū)指針指向node,兩者就建立了雙向關(guān)聯(lián)。
學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Qb4y1D75J
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1qo4y1f7Uw
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1s64y1i77s
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1g84y1F7vS
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1a54y1b7hh
細(xì)心的你一定發(fā)現(xiàn)了,在acquireQueued()方法中的死循環(huán)上注釋了一般只會執(zhí)行三次,那么為什么說會執(zhí)行三次呢?
假設(shè)現(xiàn)在lock鎖已經(jīng)被某個線程獲取了,并且還在執(zhí)行同步代碼塊,沒有來得及釋放鎖。這時,線程A也來執(zhí)行了業(yè)務(wù)方法,然后嘗試獲取鎖,必然獲取失敗進(jìn)而執(zhí)行入隊操作。此時,由當(dāng)前線程包裝的Node節(jié)點占據(jù)隊尾,隊頭是初始化的一個“空”節(jié)點。
參數(shù)pred 實際上就是head節(jié)點(狀態(tài)值為0)。第一次循環(huán),執(zhí)行else邏輯,將前驅(qū)節(jié)點pred的狀態(tài)值設(shè)置為SIGNAL,注意返回的是false,這將導(dǎo)致當(dāng)前方法所在if判斷直接結(jié)束;第二次循環(huán),首個if判斷生效,返回true,執(zhí)行parkAndCheckInterrupt(),阻塞線程;直到線程被喚醒后,開啟第三次循環(huán),獲取鎖成功,直接返回,結(jié)束死循環(huán)。
注意:以上描述,隊列中不存在已取消的節(jié)點并且鎖不會被爭搶,故而說是一般情況下。

不得不說,AQS的方法名取的還是很貼切的,基本上見名知意了。阻塞當(dāng)前線程并返回線程中斷狀態(tài)!
我們先來了解一下 LockSupport.park(this)的功能點:
阻塞當(dāng)前線程的執(zhí)行,且不會釋放當(dāng)前線程占有的鎖資源;
可以被另一個線程調(diào)用 LockSupport.unpark()方法喚醒;
底層調(diào)用 Unsafe的native方法。

3.2.4 總結(jié)
ReentranLock的一套加鎖流程總結(jié)下來,就是嘗試獲取鎖,獲取成功,更新鎖狀態(tài)、設(shè)置獨占線程;獲取失敗,將當(dāng)前線程包裝成一個Node節(jié)點,加入到AQS內(nèi)部維護(hù)的一個鏈表的尾部,最后阻塞當(dāng)前線程,直到被喚醒,再次嘗試獲取鎖(非公平鎖,存在被爭搶的可能性)。
學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Qb4y1D75J
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1qo4y1f7Uw
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1s64y1i77s
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1g84y1F7vS
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1a54y1b7hh
3.3 解鎖 lock.unlock()
加鎖時自我阻塞的線程是如何被喚醒的,觸發(fā)的機制又是怎樣的?
接下來,讓我們一步步的分析ReentranLock的另一個重要組件。

需要注意的是加鎖和解鎖的次數(shù)要一致,不然將會導(dǎo)致隊列中的等候線程無法被喚醒。
3.3.2 unparkSuccessor

預(yù)喚醒的節(jié)點s 是head的后驅(qū)節(jié)點(FIFO)。
但是如果s 為空或者已取消時,就需要從隊列中找出一個正常的節(jié)點。怎么找呢? 以隊尾節(jié)點為起始點,向前遍歷,最終s指向的是隊列中最靠近隊頭的某個正常節(jié)點。
隨后,通過調(diào)用 LockSupport.unpark()的方式,喚醒 s節(jié)點持有的線程。
至此,ReentranLock一套完整的加鎖解鎖流程就分析完畢了~
學(xué)習(xí)更多,請點擊:https://www.bilibili.com/video/BV1Qb4y1D75J
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1qo4y1f7Uw
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1s64y1i77s
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1g84y1F7vS
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?https://www.bilibili.com/video/BV1a54y1b7hh
作者:Hey Max!
鏈接:https://juejin.cn/post/6953154315603640351
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。