日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java多线程系列(十一):ReentrantReadWriteLock的实现原理与锁获取详解

發布時間:2024/7/5 java 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java多线程系列(十一):ReentrantReadWriteLock的实现原理与锁获取详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我們繼續Java多線程與并發系列之旅,之前我們分享了Synchronized 和 ReentrantLock 都是獨占鎖,即在同一時刻只有一個線程獲取到鎖。

然而在有些業務場景中,我們大多在讀取數據,很少寫入數據,這種情況下,如果仍使用獨占鎖,效率將及其低下。

針對這種情況,Java提供了讀寫鎖——ReentrantReadWriteLock。

有點類似MySQL數據庫為代表的讀寫分離機制,既然我們知道了讀寫鎖是用于讀多寫少的場景。那問題來了,ReentrantReadWriteLock是怎樣來實現的呢,它與ReentrantLock的實現又有什么的區別呢?

帶著這些疑問,Mike將通過本篇為大家剖析其中的緣由。

本文作者:MikeChen,10年+大廠架構師、CTO,持續創作、免費分享【BAT架構技術專題500+期】。

文章目錄

  • ReentrantReadWriteLock簡介
  • ReentrantReadWriteLock特性
  • ReentrantReadWriteLock的主要成員
  • ReentrantReadWriteLock的實現原理
  • ReentrantReadWriteLock寫鎖和讀鎖的獲取與釋放

ReentrantReadWriteLock簡介

很多情況下有這樣一種場景:對共享資源有讀和寫的操作,且寫操作沒有讀操作那么頻繁。

在沒有寫操作的時候,多個線程同時讀一個資源沒有任何問題,所以應該允許多個線程同時讀取共享資源,但是如果一個線程想去寫這些共享資源,就不應該允許其他線程對該資源進行讀和寫的操作了。

針對這種場景,JAVA的并發包提供了讀寫鎖ReentrantReadWriteLock,它表示兩個鎖,一個是讀操作相關的鎖,稱為共享鎖;一個是寫相關的鎖,稱為排他鎖。

ReentrantReadWriteLock特性

  • 公平性:讀寫鎖支持非公平和公平的鎖獲取方式,非公平鎖的吞吐量優于公平鎖的吞吐量,默認構造的是非公平鎖
  • 可重入:在線程獲取讀鎖之后能夠再次獲取讀鎖,但是不能獲取寫鎖,而線程在獲取寫鎖之后能夠再次獲取寫鎖,同時也能獲取讀鎖
  • 鎖降級:線程獲取寫鎖之后獲取讀鎖,再釋放寫鎖,這樣實現了寫鎖變為讀鎖,也叫鎖降級

ReentrantReadWriteLock的主要成員和結構圖

1. ReentrantReadWriteLock的繼承關系

public interface ReadWriteLock {/*** Returns the lock used for reading.** @return the lock used for reading.*/Lock readLock();/*** Returns the lock used for writing.** @return the lock used for writing.*/Lock writeLock(); }

讀寫鎖 ReadWriteLock

讀寫鎖維護了一對相關的鎖,一個用于只讀操作,一個用于寫入操作。

只要沒有寫入,讀取鎖可以由多個讀線程同時保持,寫入鎖是獨占的。


2.ReentrantReadWriteLock的核心變量


ReentrantReadWriteLock類包含三個核心變量:

  • ReaderLock:讀鎖,實現了Lock接口
  • WriterLock:寫鎖,也實現了Lock接口
  • Sync:繼承自AbstractQueuedSynchronize(AQS),可以為公平鎖FairSync 或 非公平鎖NonfairSync
  • 3.ReentrantReadWriteLock的成員變量和構造函數

    /** 內部提供的讀鎖 */private final ReentrantReadWriteLock.ReadLock readerLock;/** 內部提供的寫鎖 */private final ReentrantReadWriteLock.WriteLock writerLock;/** AQS來實現的同步器 */final Sync sync;/*** Creates a new {@code ReentrantReadWriteLock} with* 默認創建非公平的讀寫鎖*/public ReentrantReadWriteLock() {this(false);}/*** Creates a new {@code ReentrantReadWriteLock} with* the given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantReadWriteLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();readerLock = new ReadLock(this);writerLock = new WriteLock(this);}

    ReentrantReadWriteLock的核心實現

    ReentrantReadWriteLock實現關鍵點,主要包括:

    • 讀寫狀態的設計
    • 寫鎖的獲取與釋放
    • 讀鎖的獲取與釋放
    • 鎖降級

    1.讀寫狀態的設計

    之前談ReentrantLock的時候,Sync類是繼承于AQS,主要以int state為線程鎖狀態,0表示沒有被線程占用,1表示已經有線程占用。

    同樣ReentrantReadWriteLock也是繼承于AQS來實現同步,那int state怎樣同時來區分讀鎖和寫鎖的?

    如果在一個整型變量上維護多種狀態,就一定需要“按位切割使用”這個變量,ReentrantReadWriteLock將int類型的state將變量切割成兩部分:

    • 高16位記錄讀鎖狀態
    • 低16位記錄寫鎖狀態
    abstract static class Sync extends AbstractQueuedSynchronizer {// 版本序列號private static final long serialVersionUID = 6317671515068378041L; // 高16位為讀鎖,低16位為寫鎖static final int SHARED_SHIFT = 16;// 讀鎖單位static final int SHARED_UNIT = (1 << SHARED_SHIFT);// 讀鎖最大數量static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;// 寫鎖最大數量static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;// 本地線程計數器private transient ThreadLocalHoldCounter readHolds;// 緩存的計數器private transient HoldCounter cachedHoldCounter;// 第一個讀線程private transient Thread firstReader = null;// 第一個讀線程的計數private transient int firstReaderHoldCount; }

    2.寫鎖的獲取與釋放

    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();int c = getState();//獲取獨占鎖(寫鎖)的被獲取的數量int w = exclusiveCount(c);if (c != 0) {// (Note: if c != 0 and w == 0 then shared count != 0)//1.如果同步狀態不為0,且寫狀態為0,則表示當前同步狀態被讀鎖獲取//2.或者當前擁有寫鎖的線程不是當前線程if (w == 0 || current != getExclusiveOwnerThread())return false;if (w + exclusiveCount(acquires) > MAX_COUNT)throw new Error("Maximum lock count exceeded");// Reentrant acquiresetState(c + acquires);return true;}if (writerShouldBlock() ||!compareAndSetState(c, c + acquires))return false;setExclusiveOwnerThread(current);return true;}

    1)c是獲取當前鎖狀態,w是獲取寫鎖的狀態。

    2)如果鎖狀態不為零,而寫鎖的狀態為0,則表示讀鎖狀態不為0,所以當前線程不能獲取寫鎖?;蛘哝i狀態不為零,而寫鎖的狀態也不為0,但是獲取寫鎖的線程不是當前線程,則當前線程不能獲取寫鎖。

    3)寫鎖是一個可重入的排它鎖,在獲取同步狀態時,增加了一個讀鎖是否存在的判斷。

    寫鎖的釋放與ReentrantLock的釋放過程類似,每次釋放將寫狀態減1,直到寫狀態為0時,才表示該寫鎖被釋放了。

    3.讀鎖的獲取與釋放

    protected final int tryAcquireShared(int unused) {for(;;) {int c = getState();int nextc = c + (1<<16);if(nextc < c) {throw new Error("Maxumum lock count exceeded");}if(exclusiveCount(c)!=0 && owner != Thread.currentThread())return -1;if(compareAndSetState(c,nextc))return 1;} }

    1)讀鎖是一個支持重進入的共享鎖,可以被多個線程同時獲取。

    2)在沒有寫狀態為0時,讀鎖總會被成功獲取,而所做的也只是增加讀狀態(線程安全)

    3)讀狀態是所有線程獲取讀鎖次數的總和,而每個線程各自獲取讀鎖的次數只能選擇保存在ThreadLocal中,由線程自身維護。

    讀鎖的每次釋放均減小狀態(線程安全的,可能有多個讀線程同時釋放鎖),減小的值是1<<16。


    4.鎖降級

    降級是指當前把持住寫鎖,再獲取到讀鎖,隨后釋放(先前擁有的)寫鎖的過程。

    鎖降級過程中的讀鎖的獲取是否有必要,答案是必要的。主要是為了保證數據的可見性,如果當前線程不獲取讀鎖而直接釋放寫鎖,假設此刻另一個線程獲取的寫鎖,并修改了數據,那么當前線程就步伐感知到線程T的數據更新,如果當前線程遵循鎖降級的步驟,那么線程T將會被阻塞,直到當前線程使數據并釋放讀鎖之后,線程T才能獲取寫鎖進行數據更新。


    5.讀鎖與寫鎖的整體流程

    ReentrantReadWriteLock總結

    本篇詳細介紹了ReentrantReadWriteLock的特征、實現、鎖的獲取過程,通過4個關鍵點的核心設計:

    • 讀寫狀態的設計
    • 寫鎖的獲取與釋放
    • 讀鎖的獲取與釋放
    • 鎖降級

    從而才能實現:共享資源有讀和寫的操作,且寫操作沒有讀操作那么頻繁的應用場景。

    你可能也喜歡:

  • Redis系列教程(八):分布式鎖的由來、及Redis分布式鎖的實現詳解
  • Java多線程系列(八):ConcurrentHashMap的實現原理(JDK1.7和JDK1.8)
  • Java多線程系列(一):最全面的Java多線程學習概述
  • Java多線程系列(六):深入詳解Synchronized同步鎖的底層實現
  • Java多線程系列(十):源碼剖析AQS的實現原理
  • Java多線程系列(四):4種常用Java線程鎖的特點,性能比較、使用場景

  • 總結

    以上是生活随笔為你收集整理的Java多线程系列(十一):ReentrantReadWriteLock的实现原理与锁获取详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。