Java多线程——重入锁ReentrantLock源码阅读
上一章《AQS源碼閱讀》講了AQS框架,這次講講它的應(yīng)用類(注意不是子類實(shí)現(xiàn),待會(huì)細(xì)講)。
ReentrantLock,顧名思義重入鎖,但什么是重入,這個(gè)鎖到底是怎樣的,我們來(lái)看看類的注解說(shuō)明
ReentrantLock與隱式鎖synchronized功能相同,但ReentrantLock更具有擴(kuò)展性。
《鎖優(yōu)化》里提到Java在1.6對(duì)隱式鎖synchronized做了鎖的優(yōu)化,使其性能與顯式鎖性能相差無(wú)異。所以在兩者的選擇上,更多的是考慮用法,以及功能上的擴(kuò)展。
ReentrantLock是線程獨(dú)占的,不能與其他線程共享。所謂的重入,就是當(dāng)本線程想再次獲得鎖,不需要重新申請(qǐng),它本身就已經(jīng)鎖了,即重入該鎖。
為什么會(huì)允許鎖重入呢?因?yàn)樵摼€程已經(jīng)擁有鎖了,不會(huì)受其他線程干擾,那么里面的共享變量就不會(huì)因?yàn)槎嗑€程執(zhí)行造成線程不安全。相當(dāng)于代碼已經(jīng)在串行執(zhí)行了,沒(méi)必要再申請(qǐng)多余的鎖了,而是重入當(dāng)前的鎖。
ReentrantLock會(huì)提供一個(gè)公平鎖的模式,如果選擇這個(gè)模式,會(huì)盡量使得獲取鎖是公平的,先來(lái)先得,但不一定嚴(yán)格按順序。
如果選擇了公平鎖,性能上會(huì)比不使用(默認(rèn))低一些。沒(méi)有一定保證順序,同時(shí)也降性能,所以如果沒(méi)有特別的要求,盡量使用默認(rèn)的非公平鎖。
現(xiàn)在基于以上的認(rèn)識(shí),來(lái)看看ReentrantLock的基本實(shí)現(xiàn)吧。
ReentrantLock概覽
ReentrantLock是實(shí)現(xiàn)Lock接口的。所以主要的方法就是Lock接口定義的方法,包括lock()、tryLock()、unlock()、newCondition()等。
lock()與tryLock()的區(qū)別就是前者會(huì)一直等到直到獲取鎖,后者則是嘗試在當(dāng)時(shí)獲取鎖,不會(huì)重復(fù)去申請(qǐng)獲取。
這個(gè)newCondition()感覺比較突兀,看起來(lái)完全不了解有什么用,和Lock有什么關(guān)系,我們后面再詳細(xì)了解。
ReentrantLock里面有一個(gè)最核心的成員變量,sync。sync的類型就是內(nèi)部類Sync。它是AQS的子類,也就是說(shuō)它就是實(shí)現(xiàn)ReentrantLock同步的工具。而FairSync和unFairSync則是Sync的子類,封裝了是否公平的功能,用于賦值給sync成員變量。
Sync同步器
Sync是繼承上文所介紹的AQS,是ReentrantLock里面的NonfairSync和FairSync的父類。
看注解可以知道,Sync用了AQS的state(狀態(tài)原子值)來(lái)標(biāo)識(shí)鎖被持有的數(shù)量。
在AQS中,tryRelease()是沒(méi)有定義的,所以在Sync中重寫了。
先判斷下申請(qǐng)解鎖的線程是否獨(dú)占鎖的線程,否則拋出異常退出。
然后計(jì)算新的state值,用當(dāng)前state減去releases值。對(duì)于state值和releases值到底是多少,這里可以先留個(gè)懸念,但大家可以思考下上面注解的定義也可以大概猜出來(lái)。
最后判斷新state值是否為0,為0則沒(méi)有線程占用,所以設(shè)當(dāng)前獨(dú)占線程為空,并且更新state。這里更新state值并不需要用CAS原子操作,因?yàn)橹挥幸粋€(gè)線程會(huì)占用這個(gè)鎖,不是這個(gè)線程都異常退出了。
AQS中核心的tryAcquire()方法并沒(méi)有在這里實(shí)現(xiàn),因?yàn)樽宇怤onfaiSync和FairSync的實(shí)現(xiàn)并不一樣。但這里同樣需要用到nonfairTryAcquire,所以抽象出來(lái)了。但為什么同樣需要,暫時(shí)不得而知,帶著問(wèn)題后面再看看。
先判斷當(dāng)前鎖的state是否為0,為0則表示沒(méi)人獲取,然后通過(guò)CAS更新為acquires值(依然不知道值是多少),同時(shí)更新當(dāng)前線程為鎖的獨(dú)占線程。
如果state不為0,則表示有線程已經(jīng)占有了。但可能占有的線程是當(dāng)前線程,那么當(dāng)前的state會(huì)加上acquires值。
這里很容易就看出來(lái)state就是代表重入的次數(shù)!所以上面的謎題就解開了,releases,aquires都是代表每次申請(qǐng)的值,在ReentrantLock肯定都是1,他們的計(jì)算總值就是原子值state。
如果state不為0,也不是被當(dāng)前線程占用,那么返回false獲取失敗。
NonfairSync
沒(méi)啥特別的,直接調(diào)用Sync的方法。也沒(méi)做修改。
FairSync
公平鎖的同步器。只有當(dāng)遞歸調(diào)用或者沒(méi)有其他等待者,再或者他自己本身排第一才能獲取鎖。
這話比較繞口,大概意思應(yīng)該是不停地輪詢申請(qǐng)鎖,直到自己排到隊(duì)列的第一才能獲取。
乍看一看,這個(gè)方法基本和父類Sync的nonfairTryAcquire()一樣,唯一不同點(diǎn)就是在沒(méi)有線程占用的時(shí)候(state=0),多了個(gè)!hasQueuedPredecessors()前置判斷。
這個(gè)方法用來(lái)判斷是否隊(duì)列為空,或者當(dāng)前線程是否在隊(duì)列的最前面。
所以公平鎖模式下,想要能獲取鎖,除非自己排在隊(duì)列的最前面。
綜上看,FairSync根本沒(méi)有調(diào)用到nonfairTryAcquire(),為何說(shuō)子類都需要用到呢?繼續(xù)留著懸念,后面解答。
@ReservedStackAccess
可以看到上面介紹的tryAcquire()和tryRelease()都有@ReservedStackAccess。這個(gè)注解到底有什么用?
查找了下資料,這個(gè)是JEP 270添加的新注解。它會(huì)保護(hù)被注解的方法,通過(guò)添加一些額外的空間,防止在多線程運(yùn)行的時(shí)候出現(xiàn)棧溢出。具體看下圖
lock()、tryLock()成員函數(shù)
ReentrantLock里面的lock()方法是調(diào)用成員變量sync的acquire()。
無(wú)論是否公平鎖都是直接調(diào)用AQS的acquire()方法,不過(guò)就是各自有tryAcuqire()的重寫,即上文所說(shuō)的內(nèi)容。
參數(shù)1,是透?jìng)鹘otryAcquire()的,所以這里代表是入鎖一次的意思。
而tryLock()調(diào)用的是成員變量sync的nonfairTryAcquire()。上文說(shuō)到Sync內(nèi)部類抽象了這個(gè)方法出來(lái),說(shuō)到子類都會(huì)用到,說(shuō)的正是tryLock()方法需要用到。
所以顯而易見的,無(wú)論是否公平鎖,調(diào)用tryLock()都是用的非公平鎖的方法。為什么呢?
因?yàn)閠ryLock()的try只是嘗試,無(wú)論是否公平,對(duì)于方法來(lái)說(shuō)沒(méi)有必要,只是嘗試申請(qǐng)的時(shí)候能否獲取鎖而已。
至于其他成員函數(shù),大都是圍繞獲取線程和隊(duì)列的狀態(tài),沒(méi)什么特別的,在這里不再贅述,有興趣的可以看看源碼。
總結(jié)
回顧下要點(diǎn)
上文提到的newCondition()還沒(méi)有涉及到,等后續(xù)再起一章節(jié)說(shuō)下這個(gè)Condition。
如果覺得還不錯(cuò),請(qǐng)關(guān)注公眾號(hào):Zack說(shuō)碼
總結(jié)
以上是生活随笔為你收集整理的Java多线程——重入锁ReentrantLock源码阅读的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 2019年,ALEXA将会走向何方?
- 下一篇: c# maiform父窗体改变动态的gr