ReentrantReadWriteLock源碼解析

      網友投稿 582 2025-04-09

      上回說到ReentrantLock,今天來談談讀寫鎖(ReentrantLock)和其具體實現ReentrantReadWriteLock。看這篇文章前,強烈建議你回到先讀懂ReentrantLock,因為ReentrantReadWriteLock其實是在ReentrantLock的基礎上實現的,可以參考我之前的博客ReentrantLock源碼解析


      既然有了鎖,為什么還需要讀寫鎖?我們來想象下這個場景。你們小區樓下有個公告欄,有時候有人會寫個招租,有時候有人會寫個尋物啟事…… 當然一個人正在改公告欄的時候,另外一個人就不能同時改了,這里就相當于有了一把無形的鎖,我改的時候就把廣告欄“鎖住”,改完再“解鎖”,當然別人鎖住了之后我也改不了。說完了“寫”再說“讀”,一個人在讀公告欄的時候,別人就不能去寫了,這樣不禮貌,這里也相當于讀的人用一把“鎖”把公告欄給鎖了。

      如果這里讀者用的鎖和寫者用的鎖是一樣的,那么這把鎖不緊不然別人寫了,也不讓別人讀了,相當于一個人在看公告欄,別人就不能看了,這明顯不合理啊。 所以要把讀和寫用的鎖區分開來,所有讀的人共享一把鎖,寫的人獨享鎖。放到公告欄的例子上,改公告的時候同時只有一個人可以看,但讀的時候所有人可以同時讀,這樣就可以把“公告欄”這個資源的利用率最大化。

      看到這里,你應該已經理解了什么叫做“讀寫鎖”,接下來我們直接看下jdk中ReentrantReadWriteLock的實現,再次建議先閱讀ReentrantLock的具體實現。

      從類結構圖看,貌似它比ReentrantLock更復雜寫,多兩個內部類 ReadLock 和 WriteLock,看著Lock提供的api完全一樣,看來得從具體實現上來看其二者有什么樣的差異了。

      public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); }

      1

      2

      3

      4

      5

      6

      從ReentrantReadWriteLock的構造方法可以看出,它也支持公平鎖和非公平鎖,當然默認也是非公平鎖。和ReentrantLock一樣,加鎖和解鎖的實現邏輯都是在 Sync 里,所以我們重點看下Sync的實現,代碼太多這里就不貼完整代碼了,建議讀者自行打開代碼。

      Sync

      從Sync的類結構圖來看,它還是相當復雜的,別急讓我們來捋一捋,我們先從WriteLock看起(看起來會比較熟悉),看下他的lock和release的具體實現。

      @ReservedStackAccess final boolean tryWriteLock() { Thread current = Thread.currentThread(); // 1 int c = getState(); // 2 if (c != 0) { // 3 int w = exclusiveCount(c); // 4 if (w == 0 || current != getExclusiveOwnerThread()) // 5 return false; if (w == MAX_COUNT) //6. MAX_COUNT = 65535 throw new Error("Maximum lock count exceeded"); } if (!compareAndSetState(c, c + 1)) // 7 return false; setExclusiveOwnerThread(current); // 8 return true; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      如果你看過ReentrantLock的話,相信這段代碼你已經完全能看懂了。這里我再大概說下這段代碼的流程

      獲取到當前線程。

      獲取到鎖對象的state值,state是保存了鎖的狀態。

      如果state不為0,說明已經有線程加過鎖了,這時候需要額外判斷下,跳到4。 如果state為0,直接跳到 7。

      獲取到當前加寫鎖的次數,這里獲取的是state的低16位。

      c已經不為0了,如果w不為0說明有線程加了寫鎖,如果加了寫鎖的線程也不是當前線程的,加鎖就失敗了。

      這里需要額外判斷下鎖重入的次數,如果已經到65535就不能再加鎖了,后續會解釋為什么是65535。

      執行CAS操作更改鎖狀態 state。

      到這里說明加寫鎖已經成功了,把當前鎖的持有者記錄下來。

      @ReservedStackAccess final boolean tryReadLock() { Thread current = Thread.currentThread(); // 1 for (;;) { int c = getState(); // 2 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) // 3 return false; int r = sharedCount(c); // 4 if (r == MAX_COUNT) // 5 throw new Error("Maximum lock count exceeded"); if (compareAndSetState(c, c + SHARED_UNIT)) { // 6 if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != LockSupport.getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return true; } } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      讀鎖的加鎖代碼就完全不一樣了,第一眼看到的不同就是這里有個大大的無限循環,我們還是來看下讀鎖的加鎖過程。

      獲取當前線程。

      獲取鎖的state狀態值。

      如果寫鎖的加鎖次數不是0切寫鎖持有者不是當前線程,加讀鎖失敗。

      獲取讀鎖的加鎖次數,sharedCount?獲取的是state的高16位。

      如果讀鎖加鎖次數達到65535,拋Error,和寫鎖一樣,只能加65535次。

      執行到這,說明可以加鎖,使用CAS更新state成功后這里就開始記錄一些讀鎖的狀態信息,注意這里state增加值不是1,而是SHARED_UNIT(65536)。

      看完readLock和writeLock的加鎖方式就可以大體理解ReentrantReadWriteLock的實現了,原來它只是把ReentrantLock中的state分成兩部分來用,高16位記錄讀鎖狀態,低16位記錄寫鎖狀態,如下圖。

      這也是為什么上文中加鎖最大次數是65535的原因了,這也是而是SHARED_UNIT的值為65536的原因。

      理解了加鎖的代碼,解鎖部分也就好理解了,本質上是把加鎖的代碼反向執行下,代碼如下。

      @ReservedStackAccess protected final boolean tryRelease(int releases) { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0; if (free) setExclusiveOwnerThread(null); setState(nextc); return free; } @ReservedStackAccess protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); if (firstReader == current) { // assert firstReaderHoldCount > 0; if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != LockSupport.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(); 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; } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      ReentrantReadWriteLock源碼解析

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      Sync中還有一個ThreadLocalHoldCounter類,這個類的作用其實是記錄每個線程對讀鎖的加鎖測試,見名知意線程級的統計,代碼也很簡單,這里就不再貼了。

      Sync中除了上文說到的幾個加解鎖的API,其余一些API就是獲取Sync對象中各個狀態的API,沒什么好說的。

      FairSync & NonfairSync

      說完了抽象類Sync,我們來說下它的兩個具體實現 FairSync 和 NonfairSync。 這兩個實現類非常非常簡單,只是重寫了 writerShouldBlock() 和 readerShouldBlock() 方法而已,如果你已經知道什么是公平和非公平了,這地方也就很好理解了。

      static final class NonfairSync extends Sync { private static final long serialVersionUID = -8159625535654395037L; final boolean writerShouldBlock() { // 寫鎖可以始終不被等待隊列里的線程阻塞,只要當前鎖是未鎖定狀態就可以加鎖 return false; } final boolean readerShouldBlock() { //這個方法判斷隊列的head.next是否正在等待寫鎖,這個方法確保讀鎖不應該讓寫鎖始終等待,即便是非公平的,但寫鎖有更高的優先級,獲取讀鎖還是得排隊。 return apparentlyFirstQueuedIsExclusive(); } } // 公平鎖就很好理解了,只要等待隊列不為空,就得去排隊 static final class FairSync extends Sync { private static final long serialVersionUID = -2274990926593161451L; final boolean writerShouldBlock() { return hasQueuedPredecessors(); } final boolean readerShouldBlock() { return hasQueuedPredecessors(); } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      ReadLock & WriteLock

      其實看完Sync里的邏輯,基本上ReadLock和WriteLock的實現邏輯我們已經知道了。ReadLock和WriteLock只是向用戶提供里有些功能抽象(實現了Lock中的方法),封裝好了具體的實現,其實具體邏輯還是在Sync中實現。

      從類繼承關系來看,二者也只是簡單

      結論

      了解完ReentrantReadWriteLock的實現后你就會發現,它其實和ReentrantLock一樣,之前把ReentrantLock中的state切分成兩部分用,高16位作為讀鎖的state,低16位作為寫鎖。如果把ReadLock和WriteLock拉出來單獨看的話,二者都是一個ReentrantLock,只是不能像ReentrantLock那樣重入那么多次而已。

      ReentrantReadWriteLock的出現大幅提升了多讀少寫場景下的性能問題,但它依舊有自己的缺點,就是它可能會導致寫饑餓。還是拿小區公告欄的例子,如果任意時刻都有人在看公告欄,你也不好打斷人家所以你公告更新不了啊,所以想更新的人就得一直等著。

      關注我,下次和大家一起看下 StampedLock 是如何解決饑餓問題的。

      任務調度

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:請問 怎么 顯示制表位?(請問蕁麻疹會不會傳染)
      下一篇:excel刪除選定區域(excel刪除所選區域的內容)
      相關文章
      久久久久亚洲AV无码网站| 亚洲Av无码乱码在线观看性色| 亚洲乱亚洲乱妇无码麻豆| 亚洲综合色一区二区三区| 亚洲性色高清完整版在线观看| 久久精品国产亚洲av日韩| 久久精品国产96精品亚洲 | 国产亚洲中文日本不卡二区 | 亚洲熟妇色自偷自拍另类| 亚洲一区二区三区夜色| 亚洲AV无码1区2区久久| 久久国产亚洲电影天堂| 亚洲爆乳精品无码一区二区三区| 亚洲精品中文字幕无码蜜桃| 亚洲男人的天堂www| 国产亚洲精品观看91在线| 国产精品亚洲аv无码播放| 国产成人精品日本亚洲| 亚洲成AV人片一区二区| 亚洲av色福利天堂| 久久久久亚洲AV无码观看| 亚洲视频免费播放| 亚洲一区二区三区在线观看蜜桃| 91亚洲性爱在线视频| 亚洲乱码中文论理电影| 亚洲一级特黄特黄的大片| 久久久久亚洲国产| 亚洲av午夜国产精品无码中文字| 鲁死你资源站亚洲av| 亚洲成人一区二区| 自拍偷自拍亚洲精品情侣| 亚洲va无码va在线va天堂| 久久亚洲精品无码AV红樱桃| 亚洲综合在线成人一区| 亚洲成年人免费网站| 亚洲熟妇AV一区二区三区宅男| 青草久久精品亚洲综合专区| 亚洲欧洲一区二区三区| 亚洲AV永久无码精品放毛片| av无码东京热亚洲男人的天堂| 久久亚洲国产成人影院|