讀寫鎖 ReadWriteLock讀寫鎖維護(hù)了一對相關(guān)的鎖,一個用于只讀操作,一個用于寫入操作。只要沒有writer,讀取鎖可以由多個reader線程同時保持。寫入鎖是獨(dú)占的。
創(chuàng)新互聯(lián)公司服務(wù)項(xiàng)目包括饒河網(wǎng)站建設(shè)、饒河網(wǎng)站制作、饒河網(wǎng)頁制作以及饒河網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,饒河網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到饒河省份的部分城市,未來相信會繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
互斥鎖一次只允許一個線程訪問共享數(shù)據(jù),哪怕進(jìn)行的是只讀操作;讀寫鎖允許對共享數(shù)據(jù)進(jìn)行更高級別的并發(fā)訪問:對于寫操作,一次只有一個線程(write線程)可以修改共享數(shù)據(jù),對于讀操作,允許任意數(shù)量的線程同時進(jìn)行讀取。
與互斥鎖相比,使用讀寫鎖能否提升性能則取決于讀寫操作期間讀取數(shù)據(jù)相對于修改數(shù)據(jù)的頻率,以及數(shù)據(jù)的爭用——即在同一時間試圖對該數(shù)據(jù)執(zhí)行讀取或?qū)懭氩僮鞯木€程數(shù)。
讀寫鎖適用于讀多寫少的情況。
可重入讀寫鎖 ReentrantReadWriteLock
屬性ReentrantReadWriteLock 也是基于 AbstractQueuedSynchronizer 實(shí)現(xiàn)的,它具有下面這些屬性(來自Java doc文檔):
* 獲取順序:此類不會將讀取者優(yōu)先或?qū)懭胝邇?yōu)先強(qiáng)加給鎖訪問的排序。
* 非公平模式(默認(rèn)):連續(xù)競爭的非公平鎖可能無限期地推遲一個或多個reader或writer線程,但吞吐量通常要高于公平鎖。
* 公平模式:線程利用一個近似到達(dá)順序的策略來爭奪進(jìn)入。當(dāng)釋放當(dāng)前保持的鎖時,可以為等待時間最長的單個writer線程分配寫入鎖,如果有一組等待時間大于所有正在等待的writer線程的reader,將為該組分配讀者鎖。
* 試圖獲得公平寫入鎖的非重入的線程將會阻塞,除非讀取鎖和寫入鎖都自由(這意味著沒有等待線程)。
* 重入:此鎖允許reader和writer按照 ReentrantLock 的樣式重新獲取讀取鎖或?qū)懭腈i。在寫入線程保持的所有寫入鎖都已經(jīng)釋放后,才允許重入reader使用讀取鎖。
writer可以獲取讀取鎖,但reader不能獲取寫入鎖。
* 鎖降級:重入還允許從寫入鎖降級為讀取鎖,實(shí)現(xiàn)方式是:先獲取寫入鎖,然后獲取讀取鎖,最后釋放寫入鎖。但是,從讀取鎖升級到寫入鎖是不可能的。
* 鎖獲取的中斷:讀取鎖和寫入鎖都支持鎖獲取期間的中斷。
* Condition 支持:寫入鎖提供了一個 Condition 實(shí)現(xiàn),對于寫入鎖來說,該實(shí)現(xiàn)的行為與 ReentrantLock.newCondition() 提供的Condition 實(shí)現(xiàn)對 ReentrantLock 所做的行為相同。當(dāng)然,此 Condition 只能用于寫入鎖。
讀取鎖不支持 Condition,readLock().newCondition() 會拋出 UnsupportedOperationException。
* 監(jiān)測:此類支持一些確定是讀取鎖還是寫入鎖的方法。這些方法設(shè)計(jì)用于監(jiān)視系統(tǒng)狀態(tài),而不是同步控制。
實(shí)現(xiàn)AQS 回顧在之前的文章已經(jīng)提到,AQS以單個 int 類型的原子變量來表示其狀態(tài),定義了4個抽象方法( tryAcquire(int)、tryRelease(int)、tryAcquireShared(int)、tryReleaseShared(int),前兩個方法用于獨(dú)占/排他模式,后兩個用于共享模式 )留給子類實(shí)現(xiàn),用于自定義同步器的行為以實(shí)現(xiàn)特定的功能。
對于 ReentrantLock,它是可重入的獨(dú)占鎖,內(nèi)部的 Sync 類實(shí)現(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 的某個值,當(dāng)狀態(tài)大于 0 時所有wait線程阻塞,每調(diào)用一次 countDown 方法就把狀態(tài)值減 1,減為 0 時允許所有線程通過。利用了AQS的共享模式。
現(xiàn)在,要用AQS來實(shí)現(xiàn) ReentrantReadWriteLock。
一點(diǎn)思考問題
* AQS只有一個狀態(tài),那么如何表示 多個讀鎖 與 單個寫鎖 呢?
* ReentrantLock 里,狀態(tài)值表示重入計(jì)數(shù),現(xiàn)在如何在AQS里表示每個讀鎖、寫鎖的重入次數(shù)呢?
* 如何實(shí)現(xiàn)讀鎖、寫鎖的公平性呢?
【1】公平所和非公平所。
公平鎖:是指按照申請鎖的順序來獲取鎖,
非公平所:線程獲取鎖的順序不一定按照申請鎖的順序來的。
//默認(rèn)是不公平鎖,傳入true為公平鎖,否則為非公平鎖
ReentrantLock reentrantLock = new ReetrantLock();
1
2
【2】共享鎖和獨(dú)享鎖
獨(dú)享鎖:一次只能被一個線程所訪問
共享鎖:線程可以被多個線程所持有。
ReadWriteLock 讀鎖是共享鎖,寫鎖是獨(dú)享鎖。
【3】樂觀鎖和悲觀鎖。
樂觀鎖:對于一個數(shù)據(jù)的操作并發(fā),是不會發(fā)生修改的。在更新數(shù)據(jù)的時候,會嘗試采用更新,不斷重入的方式,更新數(shù)據(jù)。
悲觀鎖:對于同一個數(shù)據(jù)的并發(fā)操作,是一定會發(fā)生修改的。因此對于同一個數(shù)據(jù)的并發(fā)操作,悲觀鎖采用加鎖的形式。悲觀鎖認(rèn)為,不加鎖的操作一定會出問題,
【4】分段鎖
1.7及之前的concurrenthashmap。并發(fā)操作就是分段鎖,其思想就是讓鎖的粒度變小。
【5】偏向鎖是指一段同步代碼一直被一個線程所訪問,那么該線程會自動獲取鎖。降低獲取鎖的代價
輕量級鎖
重量級鎖
【6】自旋鎖
自旋鎖
一、公平鎖/非公平鎖
公平鎖是指多個線程按照申請鎖的順序來獲取鎖。
非公平鎖是指多個線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優(yōu)先獲取鎖。有可能,會造成優(yōu)先級反轉(zhuǎn)或者饑餓現(xiàn)象。
對于Java ReentrantLock而言,通過構(gòu)造函數(shù)指定該鎖是否是公平鎖,默認(rèn)是非公平鎖。非公平鎖的優(yōu)點(diǎn)在于吞吐量比公平鎖大。
對于Synchronized而言,也是一種非公平鎖。由于其并不像ReentrantLock是通過AQS的來實(shí)現(xiàn)線程調(diào)度,所以并沒有任何辦法使其變成公平鎖。
二、可重入鎖
可重入鎖又名遞歸鎖,是指在同一個線程在外層方法獲取鎖的時候,在進(jìn)入內(nèi)層方法會自動獲取鎖。說的有點(diǎn)抽象,下面會有一個代碼的示例。
對于Java ReentrantLock而言, 他的名字就可以看出是一個可重入鎖,其名字是Re entrant Lock重新進(jìn)入鎖。
對于Synchronized而言,也是一個可重入鎖。可重入鎖的一個好處是可一定程度避免死鎖。
synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
上面的代碼就是一個可重入鎖的一個特點(diǎn),如果不是可重入鎖的話,setB可能不會被當(dāng)前線程執(zhí)行,可能造成死鎖。
三、獨(dú)享鎖/共享鎖
獨(dú)享鎖是指該鎖一次只能被一個線程所持有。
共享鎖是指該鎖可被多個線程所持有。
對于Java
ReentrantLock而言,其是獨(dú)享鎖。但是對于Lock的另一個實(shí)現(xiàn)類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨(dú)享鎖。
讀鎖的共享鎖可保證并發(fā)讀是非常高效的,讀寫,寫讀 ,寫寫的過程是互斥的。
獨(dú)享鎖與共享鎖也是通過AQS來實(shí)現(xiàn)的,通過實(shí)現(xiàn)不同的方法,來實(shí)現(xiàn)獨(dú)享或者共享。
對于Synchronized而言,當(dāng)然是獨(dú)享鎖。
四、互斥鎖/讀寫鎖
上面講的獨(dú)享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實(shí)現(xiàn)。
互斥鎖在Java中的具體實(shí)現(xiàn)就是ReentrantLock
讀寫鎖在Java中的具體實(shí)現(xiàn)就是ReadWriteLock
五、樂觀鎖/悲觀鎖
樂觀鎖與悲觀鎖不是指具體的什么類型的鎖,而是指看待并發(fā)同步的角度。
悲觀鎖認(rèn)為對于同一個數(shù)據(jù)的并發(fā)操作,一定是會發(fā)生修改的,哪怕沒有修改,也會認(rèn)為修改。因此對于同一個數(shù)據(jù)的并發(fā)操作,悲觀鎖采取加鎖的形式。悲觀的認(rèn)為,不加鎖的并發(fā)操作一定會出問題。
樂觀鎖則認(rèn)為對于同一個數(shù)據(jù)的并發(fā)操作,是不會發(fā)生修改的。在更新數(shù)據(jù)的時候,會采用嘗試更新,不斷重新的方式更新數(shù)據(jù)。樂觀的認(rèn)為,不加鎖的并發(fā)操作是沒有事情的。
從上面的描述我們可以看出,悲觀鎖適合寫操作非常多的場景,樂觀鎖適合讀操作非常多的場景,不加鎖會帶來大量的性能提升。
悲觀鎖在Java中的使用,就是利用各種鎖。
樂觀鎖在Java中的使用,是無鎖編程,常常采用的是CAS算法,典型的例子就是原子類,通過CAS自旋實(shí)現(xiàn)原子操作的更新。
六、分段鎖
分段鎖其實(shí)是一種鎖的設(shè)計(jì),并不是具體的一種鎖,對于ConcurrentHashMap而言,其并發(fā)的實(shí)現(xiàn)就是通過分段鎖的形式來實(shí)現(xiàn)高效的并發(fā)操作。
我們以ConcurrentHashMap來說一下分段鎖的含義以及設(shè)計(jì)思想,ConcurrentHashMap中的分段鎖稱為Segment,它即類似于HashMap(JDK7與JDK8中HashMap的實(shí)現(xiàn))的結(jié)構(gòu),即內(nèi)部擁有一個Entry數(shù)組,數(shù)組中的每個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。
當(dāng)需要put元素的時候,并不是對整個hashmap進(jìn)行加鎖,而是先通過hashcode來知道他要放在那一個分段中,然后對這個分段進(jìn)行加鎖,所以當(dāng)多線程put的時候,只要不是放在一個分段中,就實(shí)現(xiàn)了真正的并行的插入。
但是,在統(tǒng)計(jì)size的時候,可就是獲取hashmap全局信息的時候,就需要獲取所有的分段鎖才能統(tǒng)計(jì)。
分段鎖的設(shè)計(jì)目的是細(xì)化鎖的粒度,當(dāng)操作不需要更新整個數(shù)組的時候,就僅僅針對數(shù)組中的一項(xiàng)進(jìn)行加鎖操作。
七、偏向鎖/輕量級鎖/重量級鎖
這三種鎖是指鎖的狀態(tài),并且是針對Synchronized。在Java
5通過引入鎖升級的機(jī)制來實(shí)現(xiàn)高效Synchronized。這三種鎖的狀態(tài)是通過對象監(jiān)視器在對象頭中的字段來表明的。
偏向鎖是指一段同步代碼一直被一個線程所訪問,那么該線程會自動獲取鎖。降低獲取鎖的代價。
輕量級鎖是指當(dāng)鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。
重量級鎖是指當(dāng)鎖為輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續(xù)下去,當(dāng)自旋一定次數(shù)的時候,還沒有獲取到鎖,就會進(jìn)入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓其他申請的線程進(jìn)入阻塞,性能降低。
八、自旋鎖
在Java中,自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點(diǎn)是循環(huán)會消耗CPU。
典型的自旋鎖實(shí)現(xiàn)的例子,可以參考自旋鎖的實(shí)現(xiàn)