ReadWriteLock接口及其實(shí)現(xiàn)ReentrantReadWriteLock方法
Java并發(fā)包的locks包里的鎖基本上已經(jīng)介紹得差不多了,ReentrantLock重入鎖是個(gè)關(guān)鍵,在清楚的了解了同步器AQS的運(yùn)行機(jī)制后,實(shí)際上再分析這些鎖就會顯得容易得多,這章節(jié)主講另外一個(gè)重要的鎖——ReentrantReadWriteLock讀寫鎖。
ReentrantLock是一個(gè)獨(dú)占鎖,也就是說只能由一個(gè)線程獲取鎖,但如果場景是線程只做讀的操作呢?這樣ReentrantLock就不是很合適,讀的線程并不需要保證其線程的安全性,任何一個(gè)線程都能去獲取鎖,只有這樣才能盡可能地保證性能和效率。ReentrantReadWriteLock就是這樣的一個(gè)鎖,在其內(nèi)部分為讀鎖和寫鎖,可以有N個(gè)讀操作線程獲取到寫鎖,但是只能有1個(gè)寫操作線程獲取到寫鎖,那么可以預(yù)見的是寫鎖是共享鎖(AQS中的共享模式),讀鎖是獨(dú)占鎖(AQS中的獨(dú)占模式)。首先來看讀寫鎖的接口類:
public interface ReadWriteLock { Lock readLock(); //獲取讀鎖 Lock writeLock(); //獲取寫鎖 }
可以看到ReadWriteLock接口只定義了兩個(gè)方法,獲取讀鎖和獲取寫鎖的方法。下面是ReadWriteLock的實(shí)現(xiàn)類——ReentrantReadWriteLock。
和ReentrantLock類似,ReentrantReadWriteLock在其內(nèi)部也是通過一個(gè)內(nèi)部類Sync實(shí)現(xiàn)同步器AQS,同樣也是通過實(shí)現(xiàn)Sync實(shí)現(xiàn)公平鎖和非公平鎖,這一點(diǎn)的思路和ReentrantLock類似。在ReadWriteLock接口中獲取的讀鎖和寫鎖是怎么實(shí)現(xiàn)的呢?
//ReentrantReadWriteLock private final ReentrantReadWriteLock.ReadLock readerLock; private final ReentrantReadWriteLock.WriteLock writerLock; final Sync sync; public ReentrantReadWriteLock(){ this(false); //默認(rèn)非公平鎖 } public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); //鎖類型(公平/非公平) readerLock = new ReadLock(this); //構(gòu)造讀鎖 writerLock = new WriteLock(this); //構(gòu)造寫鎖 } …… public ReentrantReadWriteLock.WriteLock writeLock0{return writerLock;} public ReentrantReadWriteLock.ReadLock readLock0{return ReaderLock;}
//ReentrantReadWriteLock$ReadLock public static class ReadLock implements Lock { protected ReadLock(ReentrantReadwritLock lock) { sync = lock.sync; //最后還是通過Sync內(nèi)部類實(shí)現(xiàn)鎖 } …… //它實(shí)現(xiàn)的是Lock接口,其余的實(shí)現(xiàn)可以和ReentrantLock作對比,獲取鎖、釋放鎖等等 }
//ReentrantReadWriteLock$WriteLock public static class WriteLock implemnts Lock { protected WriteLock(ReentrantReadWriteLock lock) { sync = lock.sync; } …… //它實(shí)現(xiàn)的是Lock接口,其余的實(shí)現(xiàn)可以和ReentrantLock作對比,獲取鎖、釋放鎖等等 }
上面是對ReentrantReadWriteLock做了一個(gè)大致的介紹,可以看到在其內(nèi)部有好幾個(gè)內(nèi)部類,實(shí)際上讀寫鎖內(nèi)有兩個(gè)鎖——ReadLock、WriteLock,這兩個(gè)鎖都是實(shí)現(xiàn)自Lock接口,可以和ReentrantLock對比,而這兩個(gè)鎖的內(nèi)部實(shí)現(xiàn)則是通過Sync,也就是同步器AQS實(shí)現(xiàn)的,這也可以和ReentrantLock中的Sync對比。
回顧一下AQS,其內(nèi)部有兩個(gè)重要的數(shù)據(jù)結(jié)構(gòu)——一個(gè)是同步隊(duì)列、一個(gè)則是同步狀態(tài),這個(gè)同步狀態(tài)應(yīng)用到讀寫鎖中也就是讀寫狀態(tài),但AQS中只有一個(gè)state整型來表示同步狀態(tài),讀寫鎖中則有讀、寫兩個(gè)同步狀態(tài)需要記錄。所以,讀寫鎖將AQS中的state整型做了一下處理,它是一個(gè)int型變量一共4個(gè)字節(jié)32位,那么可以讀寫狀態(tài)就可以各占16位——高16位表示讀,低16位表示寫。
現(xiàn)在有一個(gè)疑問如果state的值位5,二進(jìn)制為(00000000000000000000000000000101),如何快速確定讀和寫各自的狀態(tài)呢?這就要用到位移運(yùn)算了。計(jì)算方式為:寫狀態(tài)state & 0x0000FFFF,讀狀態(tài)state >>> 16。寫狀態(tài)增加1等于state + 1,讀狀態(tài)增加1等于state + (1 << 16)。有關(guān)移位運(yùn)算可以參考《<<、>>、>>>移位操作》。
寫鎖的獲取與釋放
根據(jù)我們之前的經(jīng)驗(yàn)可以得知:AQS已經(jīng)將獲取鎖的算法骨架搭好了,只需子類實(shí)現(xiàn)tryAcquire(獨(dú)占鎖),故我們只需查看tryAcquire。
//ReentrantReadWriteLock$Sync protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread; int c = getState(); //獲取state狀態(tài) int w = exclusiveCount(c); //獲取寫狀態(tài),即 state & 0x00001111 if (c != 0) { //存在同步狀態(tài)(讀或?qū)懀飨乱徊脚袛? if (w == 0 || current != getExclusiveOwnerThread()) //寫狀態(tài)為0,但同步狀態(tài)不為0表示有讀狀態(tài),此時(shí)獲取鎖失敗,或者當(dāng)前已經(jīng)有其他寫線程獲取了鎖此時(shí)也獲取鎖失敗 return false; if (w + exclusiveCount(acquire) > MAX_COUNT) //鎖重入是否超過限制 throw new Error(“Maxium lock count exceeded”); setState(c + acquire); //記錄鎖狀態(tài) return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; //writerShouldBlock對于非公平鎖總是返回false,對于公平鎖則判斷同步隊(duì)列中是否有前驅(qū)節(jié)點(diǎn) setExclusiveOwnerThread(current); return true; }
上面是寫鎖的狀態(tài)獲取,不好理解的是writerShouldBlock方法,此方法上面有描述,非公平鎖直接返回false,而對于公平鎖則是調(diào)用hasQueuedPredecessors方法如下:
//ReentrantReadWriteLock$FairSync final boolean writerShouldBlock() { return hasQueuedPredecessors(); }
原因是為什么呢?這就要回到非公平鎖和公平鎖的區(qū)別上來了,簡單回顧一下,詳情可參考《5.Lock接口及其實(shí)現(xiàn)ReentrantLock》。對于非公平鎖,每次線程獲取鎖時(shí)首先會強(qiáng)行進(jìn)行鎖獲取操作而不管同步隊(duì)列中是否有線程,當(dāng)獲取不到時(shí)才會將線程構(gòu)造至隊(duì)尾;對于公平鎖來講,只要同步隊(duì)列中存在線程,就不會去獲取鎖,而是將線程構(gòu)造添加至隊(duì)尾。所以重新回到寫狀態(tài)的獲取上,tryAcquire方法里,前面發(fā)現(xiàn)沒有線程持有鎖,但是此時(shí)會根據(jù)鎖的不同做相應(yīng)操作,對于非公平鎖——搶鎖,對公平鎖——同步隊(duì)列中有線程,不搶鎖,添加至隊(duì)尾排隊(duì)。
寫鎖的釋放與ReentrantLock的釋放過程基本類似,畢竟都是獨(dú)占鎖,每次釋放減少寫的狀態(tài),直到減小到0就表示寫鎖已經(jīng)完全釋放。
讀鎖的獲取與釋放
同理,根據(jù)我們之前的經(jīng)驗(yàn)可以得知:AQS已經(jīng)將獲取鎖的算法骨架搭好了,只需子類實(shí)現(xiàn)tryAcquireShared(共享鎖),故我們只需查看tryAcquireShared。我們知道對于共享模式下的鎖,它能夠被多個(gè)線程同時(shí)獲取,現(xiàn)在問題來了,T1線程獲取了鎖,同步狀態(tài)state=1,此時(shí)T2也獲取了鎖,state=2,接著T1線程重入state=3,也就是說讀狀態(tài)是所有線程讀鎖次數(shù)的總和,而每個(gè)線程各自獲取讀鎖的次數(shù)只能選擇保存在ThreadLock中,由線程自身維護(hù),所以在這個(gè)地方要做一些復(fù)雜處理,源碼有點(diǎn)長,但復(fù)雜就在于每個(gè)線程保存自身獲取讀鎖的次數(shù),具體參照源碼的tryAcquireShared,仔細(xì)閱讀并結(jié)合上面對寫鎖獲取的分析不難讀懂。
讀鎖的釋放值得注意的地方在于自身維護(hù)的獲取鎖的次數(shù),以及通過移位操作減少狀態(tài)state – (1 << 16)。
以上這篇ReadWriteLock接口及其實(shí)現(xiàn)ReentrantReadWriteLock方法就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java如何接收和發(fā)送ASCII數(shù)據(jù)
這篇文章主要介紹了java如何接收和發(fā)送ASCII數(shù)據(jù)問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09基于Spring的Maven項(xiàng)目實(shí)現(xiàn)發(fā)送郵件功能的示例
這篇文章主要介紹了基于Spring的Maven項(xiàng)目實(shí)現(xiàn)發(fā)送郵件功能,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03java統(tǒng)計(jì)漢字字?jǐn)?shù)的方法示例
這篇文章主要介紹了java統(tǒng)計(jì)漢字字?jǐn)?shù)的方法,結(jié)合實(shí)例形式分析了java正則判定、字符串遍歷及統(tǒng)計(jì)相關(guān)操作技巧,需要的朋友可以參考下2017-05-05VSCode中開發(fā)JavaWeb項(xiàng)目的詳細(xì)過程(Maven+Tomcat+熱部署)
這篇文章主要介紹了VSCode中開發(fā)JavaWeb項(xiàng)目(Maven+Tomcat+熱部署),本文分步驟通過圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09Java RocketMQ 路由注冊與刪除的實(shí)現(xiàn)
這篇文章主要介紹了Java RocketMQ 路由注冊與刪除的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11IDEA里找不到Maven的有效解決辦法(小白超詳細(xì))
這篇文章主要給大家介紹了關(guān)于IDEA里找不到Maven的有效解決辦法,文中通過圖文將解決的辦法介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07