教面試官ReentrantLock源碼
783
2025-03-31
1 讀寫鎖維護了一對相關(guān)的鎖,一個用于只讀操作,一個用于寫入操作。
只要沒有writer,讀鎖可以由多個reader線程同時保持。
寫鎖是獨占的。
互斥鎖一次只允許一個線程訪問共享數(shù)據(jù),哪怕是只讀
讀寫鎖允許對共享數(shù)據(jù)進行更高性能的并發(fā)訪問
對于寫操作,一次只有一個線程(write線程)可修改共享數(shù)據(jù)
對于讀操作,允許任意數(shù)量的線程同時讀取
與互斥鎖相比,使用讀寫鎖能否提升性能則取決于讀寫操作期間讀取數(shù)據(jù)相對于修改數(shù)據(jù)的頻率,以及數(shù)據(jù)的爭用,即在同一時間試圖對該數(shù)據(jù)執(zhí)行讀取或?qū)懭氩僮鞯木€程數(shù)。
讀寫鎖適用于讀多寫少的場景。
2 可重入讀寫鎖 ReentrantReadWriteLock
2.1 屬性
ReentrantReadWriteLock 基于 AbstractQueuedSynchronizer實現(xiàn),具有如下屬性
獲取順序:此類不會將讀/寫者優(yōu)先強加給鎖訪問的排序
非公平模式(默認)
連續(xù)競爭的非公平鎖可能無限期地推遲一個或多個reader或writer線程,但吞吐量通常要高于公平鎖
公平模式
線程利用一個近似到達順序的策略來競爭進入。
當釋放當前持有的鎖時,可以為等待時間最長的單個writer線程分配寫鎖,如果有一組等待時間大于所有正在等待的writer線程的reader,將為該組分配讀鎖。
試圖獲得公平寫入鎖的非重入的線程將會阻塞,除非讀取鎖和寫入鎖都自由(這意味著沒有等待線程)。
重入
此鎖允許reader和writer按照 ReentrantLock 的樣式重新獲取讀/寫鎖。在寫線程保持的所有寫鎖都已釋放后,才允許重入reader使用讀鎖
writer可以獲取讀取鎖,但reader不能獲取寫入鎖。
鎖降級
重入還允許從寫鎖降級為讀鎖,實現(xiàn)方式是:先獲取寫鎖,然后獲取讀取鎖,最后釋放寫鎖。但是,從讀取鎖升級到寫入鎖是不可能的。
鎖獲取的中斷
讀鎖和寫鎖都支持鎖獲取期間的中斷。
Condition 支持
寫鎖提供了一個 Condition 實現(xiàn),對于寫鎖來說,該實現(xiàn)的行為與 ReentrantLock.newCondition() 提供的 Condition 實現(xiàn)對 ReentrantLock 所做的行為相同。當然,此 Condition 只能用于寫鎖。
讀鎖不支持 Condition,readLock().newCondition() 會拋UnsupportedOperationException
監(jiān)測
此類支持一些確定是讀鎖還是寫鎖的方法。這些方法設計用于監(jiān)視系統(tǒng)狀態(tài),而不是同步控制。
3 AQS
記錄當前加鎖的是哪個線程,初始化狀態(tài)下,這個變量是null
接著線程1跑過來調(diào)用ReentrantLock的lock()方法嘗試進行加鎖,這個加鎖的過程,直接就是用CAS操作將state值從0變?yōu)?。
如果之前沒人加過鎖,那么state的值肯定是0,此時線程1就可以加鎖成功。
一旦線程1加鎖成功了之后,就可以設置當前加鎖線程是自己。
線程1跑過來加鎖的一個過程
加鎖線程變量
Reentrant打頭,意思是一個可重入鎖。
可重入鎖就是你可以對一個ReentrantLock對象多次執(zhí)行l(wèi)ock()加鎖和unlock()釋放鎖,也就是可以對一個鎖加多次,叫做可重入加鎖。
看明白了那個state變量之后,就知道了如何進行可重入加鎖!
其實每次線程1可重入加鎖一次,會判斷一下當前加鎖線程就是自己,那么他自己就可以可重入多次加鎖,每次加鎖就是把state的值給累加1,別的沒啥變化。
接著,如果線程1加鎖了之后,線程2跑過來加鎖會怎么樣呢?
3.2 我們來看看鎖的互斥是如何實現(xiàn)的
線程2跑過來一下看到,哎呀!state的值不是0啊?所以CAS操作將state從0變?yōu)?的過程會失敗,因為state的值當前為1,說明已經(jīng)有人加鎖了!
接著線程2會看一下,是不是自己之前加的鎖啊?當然不是了,“加鎖線程”這個變量明確記錄了是線程1占用了這個鎖,所以線程2此時就是加鎖失敗。
一起來感受一下線程2的絕望心路
接著,線程2會將自己放入AQS中的一個等待隊列,因為自己嘗試加鎖失敗了,此時就要將自己放入隊列中來等待,等待線程1釋放鎖之后,自己就可以重新嘗試加鎖了
所以大家可以看到,AQS是如此的核心!AQS內(nèi)部還有一個等待隊列,專門放那些加鎖失敗的線程!
同樣,給大家來一張圖,一起感受一下:
接著,線程1在執(zhí)行完自己的業(yè)務邏輯代碼之后,就會釋放鎖!
他釋放鎖的過程非常的簡單,就是將AQS內(nèi)的state變量的值遞減1,如果state值為0,則徹底釋放鎖,會將“加鎖線程”變量也設置為null!
附圖
接下來,會從等待隊列的隊頭喚醒線程2重新嘗試加鎖。
好!線程2現(xiàn)在就重新嘗試加鎖,這時還是用CAS操作將state從0變?yōu)?,此時就會成功,成功之后代表加鎖成功,就會將state設置為1。
此外,還要把“加鎖線程”設置為線程2自己,同時線程2自己就從等待隊列中出隊了。
最后再來一張圖,大家來看看這個過程。
ReentrantLock,它是可重入的獨占鎖,內(nèi)部的 Sync 類實現(xiàn)了 tryAcquire(int)、tryRelease(int) 方法,并用狀態(tài)的值來表示重入次數(shù),加鎖或重入鎖時狀態(tài)加 1,釋放鎖時狀態(tài)減 1,狀態(tài)值等于 0 表示鎖空閑。
CountDownLatch,它是一個關(guān)卡,在條件滿足前阻塞所有等待線程,條件滿足后允許所有線程通過。內(nèi)部類 Sync 把狀態(tài)初始化為大于 0 的某個值,當狀態(tài)大于 0 時所有wait線程阻塞,每調(diào)用一次 countDown 方法就把狀態(tài)值減 1,減為 0 時允許所有線程通過。利用了AQS的共享模式。
4 AQS只有一個狀態(tài),那么如何表示 多個讀鎖 與 單個寫鎖
ReentrantLock 里,狀態(tài)值表示重入計數(shù)
現(xiàn)在如何在AQS里表示每個讀鎖、寫鎖的重入次數(shù)呢
如何實現(xiàn)讀鎖、寫鎖的公平性呢
一個狀態(tài)是沒法既表示讀鎖,又表示寫鎖的,顯然不夠用啊,那就辦成兩份用了!
狀態(tài)的高位部分表示讀鎖,低位表示寫鎖
由于寫鎖只有一個,所以寫鎖的重入計數(shù)也解決了,這也會導致寫鎖可重入的次數(shù)減小。
由于讀鎖可以同時有多個,肯定不能再用辦成兩份用的方法來處理了
但我們有 ThreadLocal,可以把線程重入讀鎖的次數(shù)作為值存在 ThreadLocal
對于公平性的實現(xiàn),可以通過AQS的等待隊列和它的抽象方法來控制
在狀態(tài)值的另一半里存儲當前持有讀鎖的線程數(shù)。
如果讀線程申請讀鎖,當前寫鎖重入次數(shù)不為 0 時,則等待,否則可以馬上分配
如果是寫線程申請寫鎖,當前狀態(tài)為 0 則可以馬上分配,否則等待。
任務調(diào)度
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。