ReentrantLock,可重入鎖,支持一個線程對公有資源重復(fù)加鎖贾节。當(dāng)然汁汗,ReentrantLock還支持公平性的獲取鎖和非公平性的獲取鎖。
注:何謂獲取鎖的公平性栗涂?
每一個線程在獲取鎖的時候可能都會排隊(duì)等待知牌,如果在等待時間上,先獲取鎖的線程的請求一定先被滿足戴差,那么這個鎖就是公平的送爸。反之,這個鎖就是不公平的暖释。公平的獲取鎖袭厂,也就是當(dāng)前等待時間最長的線程先獲取鎖。
接下來就源碼對ReentrantLock做詳細(xì)的分析球匕。
ReentrantLock源碼分析
可重入鎖同讀寫鎖一樣纹磺,也是一種自定義同步器。接下來先從構(gòu)造函數(shù)開始亮曹,依次分析可重入鎖的具體實(shí)現(xiàn)橄杨。
構(gòu)造函數(shù)
從源碼可以看出:
ReentrantLock提供兩個構(gòu)造方法,不帶參數(shù)構(gòu)造方法和帶boolean型參數(shù)fair的構(gòu)造方法照卦;
默認(rèn)不帶參構(gòu)造方法是非公平性的獲取鎖式矫,帶參數(shù)fair的構(gòu)造方法根據(jù)fair的值判斷到底是公平性的獲取鎖還是非公平性的獲取鎖,fair == true時役耕,是公平性的獲取鎖采转,fair == false時,是非公平性的獲取鎖瞬痘。
自定義同步器Sync
ReentrantLock通過自定義同步器來實(shí)現(xiàn)鎖的獲取與釋放故慈,如何實(shí)現(xiàn)自定義同步器在前文java并發(fā)編程之AbstractQueuedSynchronizer 中已經(jīng)做了詳細(xì)的分析,在這里就不再贅述框全,有不了解的小伙伴可以去讀一讀這篇文章察绷。
Sync實(shí)現(xiàn)
對于JDK提供的兩種鎖來說,鎖的競爭其實(shí)就是競爭同步器AQS的同步狀態(tài)state津辩,鎖的釋放也就是釋放同步狀態(tài)拆撼,接下來我們就具體看看相關(guān)的源碼實(shí)現(xiàn)。
-
Sync之非公平鎖的獲取
Sync之非公平鎖的獲取
從源碼可以看出喘沿,鎖的獲取主要分這樣幾個步驟:
獲取當(dāng)前線程以及同步器狀態(tài)state情萤;
判斷state == 0是否成立:
成立,表示當(dāng)前同步器不存在線程競爭同步狀態(tài)摹恨,可以直接分配給當(dāng)前線程筋岛,設(shè)置同步狀態(tài),并將持有同步狀態(tài)的線程置為當(dāng)前線程晒哄;
否則睁宰,表示當(dāng)前同步器存在線程競爭同步狀態(tài)肪获,跳轉(zhuǎn)到步驟3。
- 判斷同步狀態(tài)線程 == 當(dāng)前線程是否成立:
成立柒傻,根據(jù)可重入的定義孝赫,當(dāng)前線程可以獲取同步狀態(tài),獲取并設(shè)置同步狀態(tài)红符;
否則青柄,線程不能獲取同步狀態(tài),返回预侯。
-
Sync之鎖的釋放
Sync之鎖的釋放
鎖的釋放其實(shí)就是同步狀態(tài)state的釋放致开,從源碼可以看出:
計(jì)算新的同步狀態(tài)c;
如果c == 0萎馅,表明當(dāng)前同步狀態(tài)釋放后無線程占有該同步狀態(tài)双戳,設(shè)置持有同步狀態(tài)線程為null;
設(shè)置同步狀態(tài)糜芳。
-
Sync之其他重要方法
當(dāng)然飒货,Sync也提供一些額外的方法便于使用者獲取鎖的持有者以及判斷當(dāng)前是否加鎖等。
Sync之其它重要方法
從Sync的具體實(shí)現(xiàn)可以看出峭竣,針對重進(jìn)入:
鎖的再次獲忍粮ā:記錄獲取鎖的線程,每次獲取鎖都需要去識別獲取鎖的線程是否為當(dāng)前站有所的線程皆撩,如果是扣墩,可以再次成功獲取,否則毅访,不能獲取盘榨;
鎖的最終釋放:線程重復(fù)多次獲取了鎖喻粹,隨后再多次釋放鎖,加鎖其實(shí)是同步狀態(tài)state進(jìn)行自增草巡,當(dāng)然守呜,state也就表示當(dāng)前鎖被相同線程獲取的次數(shù);鎖在釋放時山憨,state會自減查乒,當(dāng)state等于0時表示當(dāng)前線程獲取的鎖已經(jīng)成功被釋放。
公平鎖的實(shí)現(xiàn)
公平性其實(shí)僅僅是針對鎖的獲取而言郁竟,如果一個鎖是公平的玛迄,那個鎖的獲取的順序就應(yīng)該滿足請求的時間順序,換言之也就是FIFO棚亩。對于非公平鎖的獲取nonfairTryAcquire方法的具體實(shí)現(xiàn)蓖议,非公平鎖其實(shí)只需要CAS設(shè)置同步狀態(tài)成功虏杰,當(dāng)前線程也就獲取鎖成功,但是公平鎖的獲取就不一樣了勒虾。
從源碼可以看出:
公平鎖的獲取多了一個判斷(在源碼中用綠色線標(biāo)注出來的部分)hasQueuedPredecessors()方法纺阔,接下來來看看這個方法到底做了什么來保證了鎖獲取的FIFO。
-
hasQueuedPredecessors實(shí)現(xiàn)
hasQueuedPredecessors實(shí)現(xiàn)
該方法是AbstractQueuedSynchronizer提供的一個方法修然,它可以判斷當(dāng)前等待隊(duì)列是否有線程在等待笛钝,如果有返回true,反之返回false愕宋;
在公平鎖的獲取之前加入該判斷是為了確定當(dāng)前線程是否有前驅(qū)節(jié)點(diǎn)玻靡,如果有,表示有線程比當(dāng)前線程更早的請求獲取鎖掏婶,需要等待前驅(qū)線程獲取并釋放時候才能獲取鎖啃奴。
公平鎖與非公平鎖的區(qū)別
之前做過一個測試,5個線程分別獲取2次公平鎖與非公平鎖雄妥,打印獲取鎖的線程和正在等待的線程最蕾,測試結(jié)果如下:
從對比結(jié)果可以看出,非公平鎖出現(xiàn)了一個線程連續(xù)獲取鎖的情況老厌,那為什么會出現(xiàn)這個情況呢瘟则?回想一下非公平鎖的獲取,當(dāng)一個線程請求獲取鎖的時候枝秤,只需要獲取同步狀態(tài)即可成功的獲取鎖醋拧,在這個前提下,剛釋放鎖的線程再次獲取同步狀態(tài)的幾率會非常大淀弹,這就會使得其他線程只能在同步隊(duì)列中等待丹壕,這樣就可能會導(dǎo)致線程饑餓,但是薇溃,明顯知道它會有這個缺點(diǎn)菌赖,為什么還要被設(shè)置成默認(rèn)的呢?我們再來觀察下上圖的結(jié)果沐序,可以發(fā)現(xiàn)琉用,公平鎖在測試中進(jìn)行了10次切換,但是非公平鎖只有5次切換策幼,這可以說明非公平鎖的開銷會更小一點(diǎn)邑时。我們再來測試一下系統(tǒng)上下文切換的次數(shù)和耗時(測試場景:10個線程,每個線程獲取10w次鎖):
從對比結(jié)果可以看出特姐,公平鎖的總耗時和總切換次數(shù)遠(yuǎn)遠(yuǎn)超過非公平鎖晶丘,公平鎖以大量的線程切換來換取了鎖獲取的FIFO原則,而非公平鎖雖然可能會導(dǎo)致線程饑餓唐含,但是它的線程切換很少铣口,在一定程度上保證了更大的吞吐量滤钱。