Java多線程——ReentrantLock源碼閱讀

上一章《AQS源碼閱讀》講了AQS框架,這次講講它的應(yīng)用類(注意不是子類實現(xiàn),待會細講)。
ReentrantLock,顧名思義重入鎖,但什么是重入,這個鎖到底是怎樣的,我們來看看類的注解說明


ReentrantLock與隱式鎖synchronized功能相同,但ReentrantLock更具有擴展性。
《鎖優(yōu)化》里提到Java在1.6對隱式鎖synchronized做了鎖的優(yōu)化,使其性能與顯式鎖性能相差無異。所以在兩者的選擇上,更多的是考慮用法,以及功能上的擴展。

ReentrantLock是線程獨占的,不能與其他線程共享。所謂的重入,就是當本線程想再次獲得鎖,不需要重新申請,它本身就已經(jīng)鎖了,即重入該鎖。
為什么會允許鎖重入呢?因為該線程已經(jīng)擁有鎖了,不會受其他線程干擾,那么里面的共享變量就不會因為多線程執(zhí)行造成線程不安全。相當于代碼已經(jīng)在串行執(zhí)行了,沒必要再申請多余的鎖了,而是重入當前的鎖。

ReentrantLock會提供一個公平鎖的模式,如果選擇這個模式,會盡量使得獲取鎖是公平的,先來先得,但不一定嚴格按順序。
如果選擇了公平鎖,性能上會比不使用(默認)低一些。沒有一定保證順序,同時也降性能,所以如果沒有特別的要求,盡量使用默認的非公平鎖。
現(xiàn)在基于以上的認識,來看看ReentrantLock的基本實現(xiàn)吧。

ReentrantLock概覽

ReentrantLock是實現(xiàn)Lock接口的。所以主要的方法就是Lock接口定義的方法,包括lock()、tryLock()、unlock()、newCondition()等。
lock()與tryLock()的區(qū)別就是前者會一直等到直到獲取鎖,后者則是嘗試在當時獲取鎖,不會重復去申請獲取。
這個newCondition()感覺比較突兀,看起來完全不了解有什么用,和Lock有什么關(guān)系,我們后面再詳細了解。



ReentrantLock里面有一個最核心的成員變量,sync。sync的類型就是內(nèi)部類Sync。它是AQS的子類,也就是說它就是實現(xiàn)ReentrantLock同步的工具。而FairSync和unFairSync則是Sync的子類,封裝了是否公平的功能,用于賦值給sync成員變量。


Sync同步器

Sync是繼承上文所介紹的AQS,是ReentrantLock里面的NonfairSync和FairSync的父類。
看注解可以知道,Sync用了AQS的state(狀態(tài)原子值)來標識鎖被持有的數(shù)量。


在AQS中,tryRelease()是沒有定義的,所以在Sync中重寫了。

先判斷下申請解鎖的線程是否獨占鎖的線程,否則拋出異常退出。
然計算新的state值,用當前state減去releases值。對于state值和releases值到底是多少,這里可以先留個懸念,但大家可以思考下上面注解的定義也可以大概猜出來。
最后判斷新state值是否為0,為0則沒有線程占用,所以設(shè)當前獨占線程為空,并且更新state。這里更新state值并不需要用CAS原子操作,因為只有一個線程會占用這個鎖,不是這個線程都異常退出了。

AQS中核心的tryAcquire()方法并沒有在這里實現(xiàn),因為子類NonfaiSync和FairSync的實現(xiàn)并不一樣。但這里同樣需要用到nonfairTryAcquire,所以抽象出來了。但為什么同樣需要,暫時不得而知,帶著問題后面再看看。
先判斷當前鎖的state是否為0,為0則表示沒人獲取,然后通過CAS更新為acquires值(依然不知道值是多少),同時更新當前線程為鎖的獨占線程。
如果state不為0,則表示有線程已經(jīng)占有了。但可能占有的線程是當前線程,那么當前的state會加上acquires值。
這里很容易就看出來state就是代表重入的次數(shù)!所以上面的謎題就解開了,releases,aquires都是代表每次申請的值,在ReentrantLock肯定都是1,他們的計算總值就是原子值state。
如果state不為0,也不是被當前線程占用,那么返回false獲取失敗。

NonfairSync

沒啥特別的,直接調(diào)用Sync的方法。也沒做修改。


FairSync

公平鎖的同步器。只有當遞歸調(diào)用或者沒有其他等待者,再或者他自己本身排第一才能獲取鎖。
這話比較繞口,大概意思應(yīng)該是不停地輪詢申請鎖,直到自己排到隊列的第一才能獲取。


乍看一看,這個方法基本和父類Sync的nonfairTryAcquire()一樣,唯一不同點就是在沒有線程占用的時候(state=0),多了個!hasQueuedPredecessors()前置判斷。

這個方法用來判斷是否隊列為空,或者當前線程是否在隊列的最前面。
所以公平鎖模式下,想要能獲取鎖,除非自己排在隊列的最前面。
綜上看,F(xiàn)airSync根本沒有調(diào)用到nonfairTryAcquire(),為何說子類都需要用到呢?繼續(xù)留著懸念,后面解答。

@ReservedStackAccess

可以看到上面介紹的tryAcquire()和tryRelease()都有@ReservedStackAccess。這個注解到底有什么用?
查找了下資料,這個是JEP 270添加的新注解。它會保護被注解的方法,通過添加一些額外的空間,防止在多線程運行的時候出現(xiàn)棧溢出。具體看下圖


lock()、tryLock()成員函數(shù)

ReentrantLock里面的lock()方法是調(diào)用成員變量sync的acquire()。
無論是否公平鎖都是直接調(diào)用AQS的acquire()方法,不過就是各自有tryAcuqire()的重寫,即上文所說的內(nèi)容。
參數(shù)1,是透傳給tryAcquire()的,所以這里代表是入鎖一次的意思。



而tryLock()調(diào)用的是成員變量sync的nonfairTryAcquire()。上文說到Sync內(nèi)部類抽象了這個方法出來,說到子類都會用到,說的正是tryLock()方法需要用到。
所以顯而易見的,無論是否公平鎖,調(diào)用tryLock()都是用的非公平鎖的方法。為什么呢?
因為tryLock()的try只是嘗試,無論是否公平,對于方法來說沒有必要,只是嘗試申請的時候能否獲取鎖而已。



至于其他成員函數(shù),大都是圍繞獲取線程和隊列的狀態(tài),沒什么特別的,在這里不再贅述,有興趣的可以看看源碼。

總結(jié)

回顧下要點

  1. ReentrantLock是一個可重入的鎖(被當前占用的線程重入)。
  2. 它有兩種模式公平與非公平,通過NonfairSync和FairSync賦值sync成員變量實現(xiàn)。
  3. 兩種模式都是AQS的子類,通過重寫tryAcquire()區(qū)別不同。公平鎖多了是否在隊列的頭的判斷。
  4. tryLock()方法沒有區(qū)分模式,都是一樣的。

上文提到的newCondition()還沒有涉及到,等后續(xù)再起一章節(jié)說下這個Condition。

如果覺得還不錯,請關(guān)注微信公眾號:Zack說碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容