摘要:不同的是它還多了內部類和內部類,以及讀寫對應的成員變量和方法。另外是給和內部類使用的。內部類前面說到的操作是分配到里面執行的。他們都是接口的實現,所以其實最像應該是這個兩個內部類。而且大體上也沒什么差異,也是用的內部類。
之前講了《AQS源碼閱讀》和《ReentrantLock源碼閱讀》,本次將延續閱讀下ReentrantReadWriteLock,建議沒看過之前兩篇文章的,先大概了解下,有些內容會基于之前的基礎上閱讀。
這個并不是ReentrantLock簡單的升級,而是落地場景的優化,我們來詳細了解下吧。
JUC包里面已經有一個ReentrantLock了,為何還需要一個ReentrantReadWriteLock呢?看看頭注解找點線索。
它是ReadWriteLock接口的實現。那看看這個接口怎么說
在實際場景中,一般來說,讀數據遠比寫數據要多。如果我們還是用獨占鎖去鎖線程避免線程不安全的話,是非常低效的,而且同時也會失去它的并發性。多線程也沒有意義了。所以ReadWriteLock就是解決這個問題所存在的。
看回ReentrantReadWriteLock的頭注解。
ReentrantReadWriteLock依然有公平鎖/非公平鎖的功能,與ReentrantLock不同在于,前者內部維護了讀鎖和寫鎖,在公平/非公平模式下,他們會一起去競爭這個鎖資源。
上圖是兩條ReentrantReadWriteLock最核心的規則。
申請讀鎖。當沒有其他寫鎖占有,或者讀鎖在隊列中排隊時間最長的,才能成功
申請寫鎖。當沒有其他線程占有讀/寫鎖的情況下,才能成功
又以上兩條規則可以推導出,
寫鎖比讀鎖要高級
有讀鎖占用可以繼續申請讀鎖,但其他線程不能申請寫鎖
有寫鎖占用其他線程讀寫都不能申請
所以扣ReadWriteLock接口的說明,可以讓讀并發,寫獨占,提高了程序的并發性。
ReentrantReadWriteLock構成看下ReentrantReadWriteLock的file struture
之前看過ReentrantLock源碼的同學肯定很熟悉這個結構,看起來相同的都是Sync同步器(AQS的子類),以及它的兩個公平/非公平子類。
不同的是它還多了ReadLock內部類和WriteLock內部類,以及讀寫對應的成員變量和方法。并且少了lock()、unlock()等方法,而是把加鎖解鎖的功能下方給這兩個子類,符合ReadWriteLock接口的定義。
Sync內部類雖然ReentrantReadWriteLock和ReentrantLock都有Sync,但其實Sync方法已經很大不同了,看下Sync的結構
對比之前ReentrantLock的Sync,最大不同在于它多了**shared()方法,用于共享鎖的獲取與釋放。
另外tryReadLock()、tryWriteLock()是給WriteLock和ReadLock內部類使用的。
上文介紹重入鎖說到state代表的是重入的次數,在讀寫鎖的語義下,state代表的讀/寫占有(重入)的次數。c為state,w為獨占重入次數。
當有線程占用鎖時(c!=0),如果沒有寫鎖(w==0)或者獨占線程不是當前線程,返回false獲取失敗。鎖的重入總數超過上限會拋出異常。
這里很容易看出來,如果有鎖占用的時候,如果只是讀鎖,依然可以申請成功。這就是讀鎖的鎖升級。
當沒有線程占用的時候,執行writerShouldBlock()判斷是否需要阻塞線程(子類實現自己的條件),不需要則CAS state值,返回成功。
讀鎖申請比寫鎖申請要復雜,有比較多沒接觸過的成員變量,判斷的語句也比較多。
先看看成員變量,從他們各自的變量注解可知
firstReader,是第一個獲取讀鎖的線程
firstReaderHoldCount,是firstReader的計數器。
cachedHoldCounter,最近一個成功獲取讀鎖的線程持有數計數器。
readHolds,當前線程重入讀鎖次數。ThreadLocal
先判斷是否有寫鎖占有,如果寫鎖不是當前線程,獲取讀鎖失敗,退出方法。
注意如果寫鎖是當前線程是可以獲取讀鎖的,因為寫鎖是獨占的,這種情況下是不會有數據與其他線程共享的問題。
滿足子類條件,也不超過總數,CAS也成功的情況下,
如果沒有讀鎖,則設firstReader為當前線程,firstReaderHoldCount為1;
如果有讀鎖,并且也是當前線程申請獲取,firstReaderHoldCount自增1;
如果有讀鎖,不是當前線程申請,取上一個成功的緩存計數器,如果這個計數器不是當前線程的,則設為當前的計數器,并且自增,返回成功。(其實就是把緩存計數器置換為當前線程的計數器)
最后不滿足條件或者CAS失敗,執行fullTryAcquireShared(current)返回。
至于這些數據算來干嘛,等后面看看release()怎么用。
其實這個方法就是用for循環輪詢解決CAS丟失和重入失敗的問題,具體代碼不細過了,有興趣可以自己找源碼看看。
這里又有Condition的蹤跡了,大概可以才行到Condition時控制鎖的行為的,取消喚醒等操作。
另外鎖會同時釋放讀鎖和寫鎖。
這個方法比較好理解的,只要是當前線程操作下,持有重入數減去釋放數為0就可以釋放了,否則失敗。
釋放讀鎖,對正在讀的線程不會有什么影響,但可以讓等待的寫線程去開始獲取寫鎖。
剩余的內容就是對tryAquireShared()計算的count數值進行釋放(自減),如果最終自減為0則釋放讀鎖成功。
前面說到ReentrantReadWriteLock的lock()、unlock()操作是分配到Write/ReadLock里面執行的。
他們都是Lock接口的實現,所以其實最像ReentrantLock應該是這個兩個內部類。而且大體上也沒什么差異,也是用Sync的內部類。
WriteLock、ReadLock最大的不同就是WriteLock用的獨占模式的方法,ReadLock用的是共享模式的方法。
具體的代碼實現基本就是上面說明的組成,下面介紹下ReentranReadWriteLock的使用。
ReentrantLock的時候比較簡單,聲明一個變量,調用lock()方法即可。
ReentrantLock rl = new ReentrantLock(); rl.lock(); rl.unlock();
但ReentranReadWriteLock并不是Lock接口的實現,所以沒有這些方法。
有的只是writeLock()、readLock(),要先調用這個方法獲取應對的鎖對象,再調用lock()。
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); rwl.readLock().lock(); rwl.readLock().unlock(); rwl.writeLock().lock(); rwl.writeLock().unlock();總結
回顧下要點
讀寫鎖ReentrantReadWriteLock,是基于多讀少寫的實際場景,提高并發性
讀寫鎖的Sync添加了共享模式的方法
讀寫鎖內置了兩個對象readLock、writeLock,用于實際的加鎖解鎖
寫鎖是獨占的,不允許其他鎖的申請
讀鎖可以并發重復申請,當有寫鎖的時候,會發生鎖升級
如果覺得還不錯,請關注公眾號:Zack說碼
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77064.html
摘要:關于,最后有兩點規律需要注意當的等待隊列隊首結點是共享結點,說明當前寫鎖被占用,當寫鎖釋放時,會以傳播的方式喚醒頭結點之后緊鄰的各個共享結點。當的等待隊列隊首結點是獨占結點,說明當前讀鎖被使用,當讀鎖釋放歸零后,會喚醒隊首的獨占結點。 showImg(https://segmentfault.com/img/remote/1460000016012293); 本文首發于一世流云的專欄:...
摘要:前言回顧前面多線程三分鐘就可以入個門了源碼剖析多線程基礎必要知識點看了學習多線程事半功倍鎖機制了解一下簡簡單單過一遍只有光頭才能變強上一篇已經將鎖的基礎簡單地過了一遍了,因此本篇主要是講解鎖主要的兩個子類那么接下來我們就開始吧一鎖首先我們來 前言 回顧前面: 多線程三分鐘就可以入個門了! Thread源碼剖析 多線程基礎必要知識點!看了學習多線程事半功倍 Java鎖機制了解一下 AQ...
摘要:性能較好是因為避免了線程進入內核的阻塞狀態請求總數同時并發執行的線程數我們首先使用聲明一個所得實例,然后使用進行加鎖和解鎖操作。 ReentrantLock與鎖 Synchronized和ReentrantLock異同 可重入性:兩者都具有可重入性 鎖的實現:Synchronized是依賴jvm實現的,ReentrantLock是jdk實現的。(我們可以理解為一個是操作系統層面的實現...
摘要:我們知道,的作用其實是對類的和的增強,是為了讓線程在指定對象上等待,是一種線程之間進行協調的工具。當線程調用對象的方法時,必須拿到和這個對象關聯的鎖。 showImg(https://segmentfault.com/img/remote/1460000016012566); 本文首發于一世流云的專欄:https://segmentfault.com/blog... 一、Reentr...
摘要:但是不管怎樣,在一個線程已經獲取鎖后,在釋放前再次獲取鎖是一個合理的需求,而且并不生硬。那么如果考慮重入,也很簡單,在加鎖時將的值累加即可,表示同一個線程重入此鎖的次數,當歸零,即表示釋放完畢。 前言 最近研究了一下juc包的源碼。在研究ReentrantReadWriteLock讀寫鎖的時候,對于其中一些細節的思考和處理以及關于提升效率的設計感到折服,難以遏制想要分享這份心得的念頭,...
閱讀 1608·2021-11-23 09:51
閱讀 1178·2019-08-30 13:57
閱讀 2257·2019-08-29 13:12
閱讀 2011·2019-08-26 13:57
閱讀 1193·2019-08-26 11:32
閱讀 978·2019-08-23 15:08
閱讀 699·2019-08-23 14:42
閱讀 3080·2019-08-23 11:41