(六)JDK源碼分析之可重入讀寫鎖ReentrantReadWriteLock

  • 概述
    可重入讀寫鎖,其實就是在重入鎖基礎上區分了一下讀寫鎖.讀寫鎖也稱爲共享鎖,也就是說可以多個線程同時獲取鎖,這樣大大提高了系統的吞吐量. 讀寫鎖中 讀鎖和讀鎖是共享的,寫鎖和讀鎖寫鎖都是互斥的.

  • 鎖的狀態如何區分讀寫鎖
    我們經過前面分析,知道在同步器中,用了一個int型來表示一個狀態,當這個int值不爲0時,說明這個時候鎖是被獲取了的,這個時候其他線程只能自旋等待, 當然到了讀寫鎖也不例外,因爲底層用的都是同一個同步器AbstractQueuedSynchronizer,所以依然是使用一個整形來表示鎖的狀態,那麼如何來分別表示讀鎖和寫鎖的狀態,看下圖
    在這裏插入圖片描述
    如圖,因爲一個int類型是四個字節,一共是32位,這個時候用的策略就是使用高16位表示讀鎖的重入次數,用低16位表示寫鎖重入次數,這樣就可以分別表示出讀寫鎖的狀態了.比如上面讀鎖的狀態是2,寫鎖的狀態是3,但是這個帶來的問題是,狀態的值縮短了一半
    在這裏插入圖片描述
    在讀寫鎖實現中使用了位運算來分別計算讀寫鎖當前的狀態值
    a.sharedCount函數返回讀鎖的當前狀態值
    b.exclusiveCount函數返回寫鎖的當前狀態值

  • 讀鎖源碼分析
    在這裏插入圖片描述
    在這裏插入圖片描述
    和重入鎖設計一樣,控制獲取鎖的主流程在同步器中,具體實現在子類中,主要分爲兩步:
    1.tryAcquireShared獲取讀鎖
    在這裏插入圖片描述在獲取讀鎖做的事情主要流程如下:
    a. 如果別的線程已經獲取寫鎖,那麼直接獲取讀鎖失敗返回-1, 如果是獲取的寫線程是當前線程,那麼可以繼續獲取讀鎖
    b.判斷讀鎖是不是應該被阻塞,這個根據非公平鎖和公平鎖實現是不一樣的; 在非公平中,因爲是非公平的,不考慮線程等待問題,直接當前在執行中的線程優先,但是爲了不導致寫飢餓,如果在隊列中有寫鎖排隊,那麼當前獲取讀鎖阻塞; 在公平鎖實現中,是根據等待時間優先來的,總是判斷隊列有沒有等待的線程,如果有,那麼阻塞當前線程,否則直接獲取讀鎖
    c.使用ThreadLocal保存當前線程獲取讀鎖的次數, 第一個線程有單獨變量保存firstReader,firstReaderHoldCount
    2.doAcquireShared獲取讀鎖失敗,加入到隊列中
    在這裏插入圖片描述

  • 寫鎖源碼分析
    在這裏插入圖片描述
    同樣的道理獲取寫鎖也是直接使用同步器的模版方法,也是先嚐試獲取鎖,如果獲取失敗,那麼加入隊列.上圖爲第一步,第二部加入隊列實現都一樣就不累贅了
    1.如果讀鎖存在,那麼獲取寫鎖失敗;如果寫鎖存在,不是當前線程獲取的,那麼獲取鎖失敗
    2.如果存在寫鎖,並且就是當前線程,那麼重入特性滿足
    3.writerShouldBlock方法,對於非公平和公平實現是不一樣的,具體和讀鎖一樣:
    a.公平鎖,查看隊列有沒有,有那麼阻塞,加入到任務隊列,否則嘗試獲取鎖
    b.非公平鎖.直接嘗試獲取鎖

  • 總結
    通過上面分析,讀寫鎖的特性:
    a.讀鎖: 如果已經存在寫鎖,那麼獲取讀鎖失敗,如果獲取了寫鎖的線程就是當前線程,那麼可以繼續獲取讀鎖
    b 寫鎖: 如果當前存在讀鎖,那麼獲取寫鎖失敗,如果獲取了讀鎖的線程是當前線程,那麼獲取寫鎖仍然失敗.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章