1.讀寫鎖的介紹
創(chuàng)新互聯(lián)是一家專注于成都網(wǎng)站設(shè)計、網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)與策劃設(shè)計,廬山網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)10年,網(wǎng)設(shè)計領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:廬山等地區(qū)。廬山做網(wǎng)站價格咨詢:18980820575
在并發(fā)場景中用于解決線程安全的問題,我們幾乎會高頻率的使用到獨占式鎖,通常使用java提供的關(guān)鍵字synchronized或者concurrents包中實現(xiàn)了Lock接口的。它們都是獨占式獲取鎖,也就是在同一時刻只有一個線程能夠獲取鎖。而在一些業(yè)務(wù)場景中,大部分只是讀數(shù)據(jù),寫數(shù)據(jù)很少,如果僅僅是讀數(shù)據(jù)的話并不會影響數(shù)據(jù)正確性(出現(xiàn)臟讀),而如果在這種業(yè)務(wù)場景下,依然使用獨占鎖的話,很顯然這將是出現(xiàn)性能瓶頸的地方。
針對這種讀多寫少的情況,java還提供了另外一個實現(xiàn)Lock接口的ReentrantReadWriteLock(讀寫鎖)。讀寫所允許同一時刻被多個讀線程訪問,但是在寫線程訪問時,所有的讀線程和其他的寫線程都會被阻塞。在分析WirteLock和ReadLock的互斥性時可以按照WriteLock與WriteLock之間,WriteLock與ReadLock之間以及ReadLock與ReadLock之間進行分析。更多關(guān)于讀寫鎖特性介紹大家可以看源碼上的介紹(閱讀源碼時最好的一種學(xué)習(xí)方式,我也正在學(xué)習(xí)中,與大家共勉),這里做一個歸納總結(jié):
公平性選擇:支持非公平性(默認(rèn))和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平;
重入性:支持重入,讀鎖獲取后能再次獲取,寫鎖獲取之后能夠再次獲取寫鎖,同時也能夠獲取讀鎖;
鎖降級:遵循獲取寫鎖,獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級成為讀鎖
要想能夠徹底的理解讀寫鎖必須能夠理解這樣幾個問題:1. 讀寫鎖是怎樣實現(xiàn)分別記錄讀寫狀態(tài)的?2. 寫鎖是怎樣獲取和釋放的?3.讀鎖是怎樣獲取和釋放的?我們帶著這樣的三個問題,再去了解下讀寫鎖。
同步組件的實現(xiàn)聚合了同步器(AQS),并通過重寫重寫同步器(AQS)中的方法實現(xiàn)同步組件的同步語義,AQS的底層實現(xiàn)分析可以。因此,寫鎖的實現(xiàn)依然也是采用這種方式。在同一時刻寫鎖是不能被多個線程所獲取,很顯然寫鎖是獨占式鎖,而實現(xiàn)寫鎖的同步語義是通過重寫AQS中的tryAcquire方法實現(xiàn)的。源碼為:
protected?final?boolean?tryAcquire(int?acquires)?{ ????/* ?????*?Walkthrough: ?????*?1\.?If?read?count?nonzero?or?write?count?nonzero ?????*????and?owner?is?a?different?thread,?fail. ?????*?2\.?If?count?would?saturate,?fail.?(This?can?only ?????*????happen?if?count?is?already?nonzero.) ?????*?3\.?Otherwise,?this?thread?is?eligible?for?lock?if ?????*????it?is?either?a?reentrant?acquire?or ?????*????queue?policy?allows?it.?If?so,?update?state ?????*????and?set?owner. ?????*/ ????Thread?current?=?Thread.currentThread(); ????//?1\.?獲取寫鎖當(dāng)前的同步狀態(tài) ????int?c?=?getState(); ????//?2\.?獲取寫鎖獲取的次數(shù) ????int?w?=?exclusiveCount(c); ????if?(c?!=?0)?{ ????????//?(Note:?if?c?!=?0?and?w?==?0?then?shared?count?!=?0) ????????//?3.1?當(dāng)讀鎖已被讀線程獲取或者當(dāng)前線程不是已經(jīng)獲取寫鎖的線程的話 ????????//?當(dāng)前線程獲取寫鎖失敗 ????????if?(w?==?0?||?current?!=?getExclusiveOwnerThread()) ????????????return?false; ????????if?(w?+?exclusiveCount(acquires)?>?MAX_COUNT) ????????????throw?new?Error("Maximum?lock?count?exceeded"); ????????//?Reentrant?acquire ????????//?3.2?當(dāng)前線程獲取寫鎖,支持可重復(fù)加鎖 ????????setState(c?+?acquires); ????????return?true; ????} ????//?3.3?寫鎖未被任何線程獲取,當(dāng)前線程可獲取寫鎖 ????if?(writerShouldBlock()?|| ????????!compareAndSetState(c,?c?+?acquires)) ????????return?false; ????setExclusiveOwnerThread(current); ????return?true; }
這段代碼的邏輯請看注釋,這里有一個地方需要重點關(guān)注,exclusiveCount(c)方法,該方法源碼為:
static?int?exclusiveCount(int?c)?{?return?c?&?EXCLUSIVE_MASK;?}
其中EXCLUSIVE_MASK為:?static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
EXCLUSIVE _MASK為1左移16位然后減1,即為0x0000FFFF。而exclusiveCount方法是將同步狀態(tài)(state為int類型)與0x0000FFFF相與,即取同步狀態(tài)的低16位。那么低16位代表什么呢?根據(jù)exclusiveCount方法的注釋為獨占式獲取的次數(shù)即寫鎖被獲取的次數(shù),現(xiàn)在就可以得出來一個結(jié)論同步狀態(tài)的低16位用來表示寫鎖的獲取次數(shù)。同時還有一個方法值得我們注意:
static?int?sharedCount(int?c)????{?return?c?>>>?SHARED_SHIFT;?}
該方法是獲取讀鎖被獲取的次數(shù),是將同步狀態(tài)(int c)右移16次,即取同步狀態(tài)的高16位,現(xiàn)在我們可以得出另外一個結(jié)論同步狀態(tài)的高16位用來表示讀鎖被獲取的次數(shù)?,F(xiàn)在還記得我們開篇說的需要弄懂的第一個問題嗎?讀寫鎖是怎樣實現(xiàn)分別記錄讀鎖和寫鎖的狀態(tài)的,現(xiàn)在這個問題的答案就已經(jīng)被我們弄清楚了,其示意圖如下圖所示:
現(xiàn)在我們回過頭來看寫鎖獲取方法tryAcquire,其主要邏輯為:當(dāng)讀鎖已經(jīng)被讀線程獲取或者寫鎖已經(jīng)被其他寫線程獲取,則寫鎖獲取失敗;否則,獲取成功并支持重入,增加寫狀態(tài)。
寫鎖釋放通過重寫AQS的tryRelease方法,源碼為:
protected?final?boolean?tryRelease(int?releases)?{ ????if?(!isHeldExclusively()) ????????throw?new?IllegalMonitorStateException(); ????//1\.?同步狀態(tài)減去寫狀態(tài) ????int?nextc?=?getState()?-?releases; ????//2\.?當(dāng)前寫狀態(tài)是否為0,為0則釋放寫鎖 ????boolean?free?=?exclusiveCount(nextc)?==?0; ????if?(free) ????????setExclusiveOwnerThread(null); ????//3\.?不為0則更新同步狀態(tài) ????setState(nextc); ????return?free; }
源碼的實現(xiàn)邏輯請看注釋,不難理解與ReentrantLock基本一致,這里需要注意的是,減少寫狀態(tài)int nextc = getState() - releases;
只需要用當(dāng)前同步狀態(tài)直接減去寫狀態(tài)的原因正是我們剛才所說的寫狀態(tài)是由同步狀態(tài)的低16位表示的。
看完了寫鎖,現(xiàn)在來看看讀鎖,讀鎖不是獨占式鎖,即同一時刻該鎖可以被多個讀線程獲取也就是一種共享式鎖。按照之前對AQS介紹,實現(xiàn)共享式同步組件的同步語義需要通過重寫AQS的tryAcquireShared方法和tryReleaseShared方法。讀鎖的獲取實現(xiàn)方法為:
protected?final?int?tryAcquireShared(int?unused)?{ ????/* ?????*?Walkthrough: ?????*?1\.?If?write?lock?held?by?another?thread,?fail. ?????*?2\.?Otherwise,?this?thread?is?eligible?for ?????*????lock?wrt?state,?so?ask?if?it?should?block ?????*????because?of?queue?policy.?If?not,?try ?????*????to?grant?by?CASing?state?and?updating?count. ?????*????Note?that?step?does?not?check?for?reentrant ?????*????acquires,?which?is?postponed?to?full?version ?????*????to?avoid?having?to?check?hold?count?in ?????*????the?more?typical?non-reentrant?case. ?????*?3\.?If?step?2?fails?either?because?thread ?????*????apparently?not?eligible?or?CAS?fails?or?count ?????*????saturated,?chain?to?version?with?full?retry?loop. ?????*/ ????Thread?current?=?Thread.currentThread(); ????int?c?=?getState(); ????//1\.?如果寫鎖已經(jīng)被獲取并且獲取寫鎖的線程不是當(dāng)前線程的話,當(dāng)前 ????//?線程獲取讀鎖失敗返回-1 ????if?(exclusiveCount(c)?!=?0?&& ????????getExclusiveOwnerThread()?!=?current) ????????return?-1; ????int?r?=?sharedCount(c); ????if?(!readerShouldBlock()?&& ????????r?代碼的邏輯請看注釋,需要注意的是?當(dāng)寫鎖被其他線程獲取后,讀鎖獲取失敗,否則獲取成功利用CAS更新同步狀態(tài)。另外,當(dāng)前同步狀態(tài)需要加上SHARED_UNIT(
(1 << SHARED_SHIFT)
即0x00010000)的原因這是我們在上面所說的同步狀態(tài)的高16位用來表示讀鎖被獲取的次數(shù)。如果CAS失敗或者已經(jīng)獲取讀鎖的線程再次獲取讀鎖時,是靠fullTryAcquireShared方法實現(xiàn)的,這段代碼就不展開說了,有興趣可以看看。3.2.讀鎖的釋放
讀鎖釋放的實現(xiàn)主要通過方法tryReleaseShared,源碼如下,主要邏輯請看注釋:
protected?final?boolean?tryReleaseShared(int?unused)?{ ????Thread?current?=?Thread.currentThread(); ????//?前面還是為了實現(xiàn)getReadHoldCount等新功能 ????if?(firstReader?==?current)?{ ????????//?assert?firstReaderHoldCount?>?0; ????????if?(firstReaderHoldCount?==?1) ????????????firstReader?=?null; ????????else ????????????firstReaderHoldCount--; ????}?else?{ ????????HoldCounter?rh?=?cachedHoldCounter; ????????if?(rh?==?null?||?rh.tid?!=?getThreadId(current)) ????????????rh?=?readHolds.get(); ????????int?count?=?rh.count; ????????if?(count?<=?1)?{ ????????????readHolds.remove(); ????????????if?(count?<=?0) ????????????????throw?unmatchedUnlockException(); ????????} ????????--rh.count; ????} ????for?(;;)?{ ????????int?c?=?getState(); ????????//?讀鎖釋放?將同步狀態(tài)減去讀狀態(tài)即可 ????????int?nextc?=?c?-?SHARED_UNIT; ????????if?(compareAndSetState(c,?nextc)) ????????????//?Releasing?the?read?lock?has?no?effect?on?readers, ????????????//?but?it?may?allow?waiting?writers?to?proceed?if ????????????//?both?read?and?write?locks?are?now?free. ????????????return?nextc?==?0; ????} }4.鎖降級
讀寫鎖支持鎖降級,遵循按照獲取寫鎖,獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級成為讀鎖,不支持鎖升級,關(guān)于鎖降級下面的示例代碼摘自ReentrantWriteReadLock源碼中:
void?processCachedData()?{ ????????rwl.readLock().lock(); ????????if?(!cacheValid)?{ ????????????//?Must?release?read?lock?before?acquiring?write?lock ????????????rwl.readLock().unlock(); ????????????rwl.writeLock().lock(); ????????????try?{ ????????????????//?Recheck?state?because?another?thread?might?have ????????????????//?acquired?write?lock?and?changed?state?before?we?did. ????????????????if?(!cacheValid)?{ ????????????????????data?=?... ????????????cacheValid?=?true; ??????????} ??????????//?Downgrade?by?acquiring?read?lock?before?releasing?write?lock ??????????rwl.readLock().lock(); ????????}?finally?{ ??????????rwl.writeLock().unlock();?//?Unlock?write,?still?hold?read ????????} ??????} ??????try?{ ????????use(data); ??????}?finally?{ ????????rwl.readLock().unlock(); ??????} ????} }
分享題目:深入理解讀寫鎖ReentrantReadWriteLock
文章路徑:http://weahome.cn/article/iieoic.html