欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java AQS中ReentrantReadWriteLock讀寫鎖的使用

 更新時間:2023年02月02日 09:12:55   作者:飛奔的小付  
ReentrantReadWriteLock稱為讀寫鎖,它提供一個讀鎖,支持多個線程共享同一把鎖。這篇文章主要講解一下ReentrantReadWriteLock的使用和應(yīng)用場景,感興趣的可以了解一下

一. 簡介

為什么會使用讀寫鎖?

日常大多數(shù)見到的對共享資源有讀和寫的操作,寫操作并沒有讀操作那么頻繁(讀多寫少),在沒有寫操作的時候,多個線程同時讀一個資源沒有任何問題,所以應(yīng)該允許多個線程同時讀取共享資源(讀讀可以并發(fā));但是如果一個線程想去寫這些共享資源,就不應(yīng)該允許其他線程對該資源進行讀和寫操作了(讀寫,寫讀,寫寫互斥)。在讀多于寫的情況下,讀寫鎖能夠提供比排它鎖更好的并發(fā)性和吞吐量。

針對這種場景JAVA的并發(fā)包提供了讀寫鎖ReentrantReadWriteLock,它內(nèi)部維護了一對相關(guān)的鎖,一個用于只讀操作,稱為讀鎖;一個用于寫入操作,稱為寫鎖。

線程進入讀鎖的前提條件:

  • 沒有其他線程的寫鎖
  • 沒有寫請求或者有寫請求,但調(diào)用線程和持有鎖的線程是同一個。

線程進入寫鎖的前提條件:

  • 沒有其他線程的讀鎖
  • 沒有其他線程的寫鎖

而讀寫鎖有以下三個重要的特性:

  • 公平選擇性:支持非公平(默認)和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平。
  • 可重入:讀鎖和寫鎖都支持線程重入。以讀寫線程為例:讀線程獲取讀鎖后,能夠再次獲取讀鎖。寫線程在獲取寫鎖之后能夠再次獲取寫鎖,同時也可以獲取讀鎖。
  • 鎖降級:遵循獲取寫鎖、再獲取讀鎖最后釋放寫鎖的次序,寫鎖能夠降級成為讀鎖。

二. 接口及實現(xiàn)類

接口

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();
}

實現(xiàn)類

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics */
    final Sync sync;
    public ReentrantReadWriteLock() {
        this(false);
    }
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

三.使用

private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock r = readWriteLock.readLock();
private Lock w = readWriteLock.writeLock();
// 讀操作上讀鎖
public Data get(String key) {
  r.lock();
  try { 
      // TODO 業(yè)務(wù)邏輯
  }finally { 
       r.unlock(); 
   }
}
// 寫操作上寫鎖
public Data put(String key, Data value) {
  w.lock();
  try { 
      // TODO 業(yè)務(wù)邏輯
  }finally { 
       w.unlock(); 
   }

四. 應(yīng)用場景

ReentrantReadWriteLock適合讀多寫少的場景。

public class Cache {
    static Map<String, Object> map = new HashMap<String, Object>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();
    // 獲取一個key對應(yīng)的value
    public static final Object get(String key) {
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
        }
    }
    // 設(shè)置key對應(yīng)的value,并返回舊的value
    public static final Object put(String key, Object value) {
        w.lock();
        try {
            return map.put(key, value);
        } finally {
            w.unlock();
        }
    }
    // 清空所有的內(nèi)容
    public static final void clear() {
        w.lock();
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }

上述示例中,Cache組合一個非線程安全的HashMap作為緩存的實現(xiàn),同時使用讀寫鎖的讀鎖和寫鎖來保證Cache是線程安全的。在讀操作get(String key)方法中,需要獲取讀鎖,這使得并發(fā)訪問該方法時不會被阻塞。寫操作put(String key,Object value)方法和clear()方法,在更新 HashMap時必須提前獲取寫鎖,當獲取寫鎖后,其他線程對于讀鎖和寫鎖的獲取均被阻塞,而只有寫鎖被釋放之后,其他讀寫操作才能繼續(xù)。Cache使用讀寫鎖提升讀操作的并發(fā)性,也保證每次寫操作對所有的讀寫操作的可見性,同時簡化了編程方式

五. 鎖降級

鎖降級指的是寫鎖降級成為讀鎖。如果當前線程擁有寫鎖,然后將其釋放,最后再獲取讀鎖,這種分段完成的過程不能稱之為鎖降級。鎖降級是指把持?。ó斍皳碛械模戞i,再獲取到讀鎖,隨后釋放(先前擁有的)寫鎖的過程。鎖降級可以幫助我們拿到當前線程修改后的結(jié)果而不被其他線程所破壞,防止更新丟失。

示例

因為數(shù)據(jù)不常變化,所以多個線程可以并發(fā)地進行數(shù)據(jù)處理,當數(shù)據(jù)變更后,如果當前線程感知到數(shù)據(jù)變化,則進行數(shù)據(jù)的準備工作,同時其他處理線程被阻塞,直到當前線程完成數(shù)據(jù)的準備工作。

private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
private volatile boolean update = false;
public void processData() {
    readLock.lock();
    if (!update) {
        // 必須先釋放讀鎖
        readLock.unlock();
        // 鎖降級從寫鎖獲取到開始
        writeLock.lock();
        try {
            if (!update) {
                // TODO 準備數(shù)據(jù)的流程(略)  
                update = true;
            }
            readLock.lock();
        } finally {
            writeLock.unlock();
        }
        // 鎖降級完成,寫鎖降級為讀鎖
    }
    try {
        //TODO  使用數(shù)據(jù)的流程(略)
    } finally {
        readLock.unlock();
    }
}

鎖降級中讀鎖的獲取是否必要呢?答案是必要的。主要是為了保證數(shù)據(jù)的可見性,如果當前線程不獲取讀鎖而是直接釋放寫鎖,假設(shè)此刻另一個線程(記作線程T)獲取了寫鎖并修改了數(shù)據(jù),那么當前線程無法感知線程T的數(shù)據(jù)更新。如果當前線程獲取讀鎖,即遵循鎖降級的步驟,則線程T將會被阻塞,直到當前線程使用數(shù)據(jù)并釋放讀鎖之后,線程T才能獲取寫鎖進行數(shù)據(jù)更新。

RentrantReadWriteLock不支持鎖升級(把持讀鎖、獲取寫鎖,最后釋放讀鎖的過程)。目的也是保證數(shù)據(jù)可見性,如果讀鎖已被多個線程獲取,其中任意線程成功獲取了寫鎖并更新了數(shù)據(jù),則其更新對其他獲取到讀鎖的線程是不可見的。

六.源碼解析

在 ReentrantLock 中,使用 Sync 的 int 類型的 state 來表示同步狀態(tài),表示鎖被一個線程重復獲取的次數(shù)。但是,讀寫鎖 ReentrantReadWriteLock 內(nèi)部維護著一對讀寫鎖,如果要用一個變量維護多種狀態(tài),需要采用“按位切割使用”的方式來維護這個變量,將其切分為兩部分:高16為表示讀,低16為表示寫。

分割之后,讀寫鎖是如何迅速確定讀鎖和寫鎖的狀態(tài)呢?通過位運算。假如當前同步狀態(tài)為S,那么:

  • 寫狀態(tài),等于 S & 0x0000FFFF(將高 16 位全部抹去)。 當寫狀態(tài)加1,等于S+1.
  • 讀狀態(tài),等于 S >>> 16 (無符號補 0 右移 16 位)。當讀狀態(tài)加1,等于S+(1<<16),也就是S+0x00010000

根據(jù)狀態(tài)的劃分能得出一個推論:S不等于0時,當寫狀態(tài)(S&0x0000FFFF)等于0時,則讀狀態(tài)(S>>>16)大于0,即讀鎖已被獲取。

通過ReentrantReadWriteLock的sync來實現(xiàn)

abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 6317671515068378041L;
        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;
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

exclusiveCount(int c) 靜態(tài)方法,獲得持有寫狀態(tài)的鎖的次數(shù)。

sharedCount(int c) 靜態(tài)方法,獲得持有讀狀態(tài)的鎖的線程數(shù)量。不同于寫鎖,讀鎖可以同時被多個線程持有。而每個線程持有的讀鎖支持重入的特性,所以需要對每個線程持有的讀鎖的數(shù)量單獨計數(shù),這就需要用到 HoldCounter 計數(shù)器

HoldCounter 計數(shù)器

讀鎖的內(nèi)在機制其實就是一個共享鎖。一次共享鎖的操作就相當于對HoldCounter 計數(shù)器的操作。獲取共享鎖,則該計數(shù)器 + 1,釋放共享鎖,該計數(shù)器 - 1。只有當線程獲取共享鎖后才能對共享鎖進行釋放、重入操作。

  static final class HoldCounter {
            int count = 0;
            final long tid = getThreadId(Thread.currentThread());
        }
        static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }

通過 ThreadLocalHoldCounter 類,HoldCounter 與線程進行綁定。HoldCounter 是綁定線程的一個計數(shù)器,而 ThreadLocalHoldCounter 則是線程綁定的 ThreadLocal。

  • HoldCounter是用來記錄讀鎖重入數(shù)的對象
  • ThreadLocalHoldCounter是ThreadLocal變量,用來存放不是第一個獲取讀鎖的線程的其他線程的讀鎖重入數(shù)對象

寫鎖的獲取

寫鎖是一個支持重進入的排它鎖。如果當前線程已經(jīng)獲取了寫鎖,則增加寫狀態(tài)。如果當前線程在獲取寫鎖時,讀鎖已經(jīng)被獲?。ㄗx狀態(tài)不為0)或者該線程不是已經(jīng)獲取寫鎖的線程, 則當前線程進入等待狀態(tài)。

寫鎖的獲取是通過重寫AQS中的tryAcquire方法實現(xiàn)的。

protected final boolean tryAcquire(int acquires) {
    //當前線程
    Thread current = Thread.currentThread();
    //獲取state狀態(tài)   存在讀鎖或者寫鎖,狀態(tài)就不為0
    int c = getState();
    //獲取寫鎖的重入數(shù)
    int w = exclusiveCount(c);
    //當前同步狀態(tài)state != 0,說明已經(jīng)有其他線程獲取了讀鎖或?qū)戞i
    if (c != 0) {
        // c!=0 && w==0 表示存在讀鎖
        // 當前存在讀鎖或者寫鎖已經(jīng)被其他寫線程獲取,則寫鎖獲取失敗
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 超出最大范圍  65535
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        //同步state狀態(tài)
        setState(c + acquires);
        return true;
    }
    // writerShouldBlock有公平與非公平的實現(xiàn), 非公平返回false,會嘗試通過cas加鎖
    //c==0 寫鎖未被任何線程獲取,當前線程是否阻塞或者cas嘗試獲取鎖 
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    //設(shè)置寫鎖為當前線程所有
    setExclusiveOwnerThread(current);
    return true;

通過源碼我們可以知道:

  • 讀寫互斥
  • 寫寫互斥
  • 寫鎖支持同一個線程重入
  • writerShouldBlock寫鎖是否阻塞實現(xiàn)取決公平與非公平的策略(FairSync和NonfairSync)

大致流程如下:

寫鎖的釋放

寫鎖釋放通過重寫AQS的tryRelease方法實現(xiàn)

protected final boolean tryRelease(int releases) {
    //若鎖的持有者不是當前線程,拋出異常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    //當前寫狀態(tài)是否為0,為0則釋放寫鎖
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;

流程如下:

讀鎖的獲取

實現(xiàn)共享式同步組件的同步語義需要通過重寫AQS的tryAcquireShared方法和tryReleaseShared方法。讀鎖的獲取實現(xiàn)方法為:

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    // 如果寫鎖已經(jīng)被獲取并且獲取寫鎖的線程不是當前線程,當前線程獲取讀鎖失敗返回-1   判斷鎖降級
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    //計算出讀鎖的數(shù)量
    int r = sharedCount(c);
    /**
    * 讀鎖是否阻塞    readerShouldBlock()公平與非公平的實現(xiàn)
    * r < MAX_COUNT: 持有讀鎖的線程小于最大數(shù)(65535)
    *  compareAndSetState(c, c + SHARED_UNIT) cas設(shè)置獲取讀鎖線程的數(shù)量
    */
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {   //當前線程獲取讀鎖
        
        if (r == 0) {  //設(shè)置第一個獲取讀鎖的線程
            firstReader = current; 
            firstReaderHoldCount = 1;  //設(shè)置第一個獲取讀鎖線程的重入數(shù)
        } else if (firstReader == current) { // 表示第一個獲取讀鎖的線程重入
            firstReaderHoldCount++;
        } else { // 非第一個獲取讀鎖的線程
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;  //記錄其他獲取讀鎖的線程的重入次數(shù)
        }
        return 1;
    }
    // 嘗試通過自旋的方式獲取讀鎖,實現(xiàn)了重入邏輯
    return fullTryAcquireShared(current);
  • 讀鎖共享,讀讀不互斥
  • 讀鎖可重入,每個獲取讀鎖的線程都會記錄對應(yīng)的重入數(shù)
  • 讀寫互斥,鎖降級場景除外
  • 支持鎖降級,持有寫鎖的線程,可以獲取讀鎖,但是后續(xù)要記得把讀鎖和寫鎖讀釋放
  • readerShouldBlock讀鎖是否阻塞實現(xiàn)取決公平與非公平的策略(FairSync和NonfairSync)

流程如下:

讀鎖的釋放

獲取到讀鎖,執(zhí)行完臨界區(qū)后,要記得釋放讀鎖(如果重入多次要釋放對應(yīng)的次數(shù)),不然會阻塞其他線程的寫操作。

讀鎖釋放的實現(xiàn)主要通過方法tryReleaseShared:

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    //如果當前線程是第一個獲取讀鎖的線程
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--; //重入次數(shù)減1
    } 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;  //重入次數(shù)減1
    }
    for (;;) {  //cas更新同步狀態(tài)
        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;
    }

流程如下:

七.總結(jié)

本文主要講解ReentrantReadWriteLock的使用,讀寫鎖設(shè)計的原理,鎖降級,應(yīng)用場景及源碼解析,重點解析了寫鎖的獲取和釋放,讀鎖的獲取和釋放,深層次的理解讀寫鎖是怎樣實現(xiàn)分別記錄讀寫狀態(tài)的,以及讀寫鎖的獲取及釋放。

到此這篇關(guān)于Java AQS中ReentrantReadWriteLock讀寫鎖的使用的文章就介紹到這了,更多相關(guān)Java ReentrantReadWriteLock內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Intellij IDEA 2017 debug斷點調(diào)試技巧(總結(jié))

    詳解Intellij IDEA 2017 debug斷點調(diào)試技巧(總結(jié))

    這篇文章主要介紹了詳解Intellij IDEA 2017 debug斷點調(diào)試技巧(總結(jié)),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-11-11
  • java線性表排序示例分享

    java線性表排序示例分享

    這篇文章主要介紹了java線性表排序示例,需要的朋友可以參考下
    2014-03-03
  • Java?Io?File文件操作基礎(chǔ)教程

    Java?Io?File文件操作基礎(chǔ)教程

    這篇文章主要介紹了Java?Io?File文件操作,在這里需要注意File只能訪問文件以及文件系統(tǒng)的元數(shù)據(jù)。如果你想讀寫文件內(nèi)容,需要使用FileInputStream、FileOutputStream或者RandomAccessFile,需要的朋友可以參考下
    2022-05-05
  • SpringBoot中的@ConditionalOnMissingBean注解使用詳解

    SpringBoot中的@ConditionalOnMissingBean注解使用詳解

    這篇文章主要介紹了SpringBoot中的@ConditionalOnMissingBean注解使用詳解,@ConditionalOnMissingBean作用在@Bean定義上,也就是說在容器加載它作用的Bean時,檢查容器中是否存在目標類型,需要的朋友可以參考下
    2024-01-01
  • SpringBoot預防XSS攻擊的實現(xiàn)

    SpringBoot預防XSS攻擊的實現(xiàn)

    XSS攻擊是一種在web應(yīng)用中的計算機安全漏洞,它允許惡意web用戶將代碼植入到提供給其它用戶使用的頁面,本文主要介紹了SpringBoot預防XSS攻擊的實現(xiàn),感興趣的可以了解一下
    2023-08-08
  • Spring:@Async注解和AsyncResult與CompletableFuture使用問題

    Spring:@Async注解和AsyncResult與CompletableFuture使用問題

    這篇文章主要介紹了Spring:@Async注解和AsyncResult與CompletableFuture使用問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • java向上轉(zhuǎn)型與向下轉(zhuǎn)型詳解

    java向上轉(zhuǎn)型與向下轉(zhuǎn)型詳解

    這篇文章主要為大家詳細介紹了java向上轉(zhuǎn)型與向下轉(zhuǎn)型,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • 最優(yōu)雅地整合 Spring & Spring MVC & MyBatis 搭建 Java 企業(yè)級應(yīng)用(附源碼)

    最優(yōu)雅地整合 Spring & Spring MVC & MyBatis 搭建 Java 企業(yè)級應(yīng)用(附源碼)

    這篇文章主要介紹了最優(yōu)雅地整合 Spring & Spring MVC & MyBatis 搭建 Java 企業(yè)級應(yīng)用(附源碼),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01
  • SpringMVC前后端參數(shù)映射

    SpringMVC前后端參數(shù)映射

    在web開發(fā)中我們都要進行前端傳參后端取參的過程,本文主要介紹了SpringMVC前后端參數(shù)映射,針對GET,?POST,?PUT,?DELETE?請求的參數(shù)該如何映射,感興趣的可以了解一下
    2023-08-08
  • 詳解在springmvc中解決FastJson循環(huán)引用的問題

    詳解在springmvc中解決FastJson循環(huán)引用的問題

    本篇文章主要介紹了在springmvc中解決FastJson循環(huán)引用的問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01

最新評論