成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

圖解ReentrantReadWriteLock實(shí)現(xiàn)分析

nemo / 3626人閱讀

摘要:鎖實(shí)現(xiàn)分析本節(jié)通過學(xué)習(xí)源碼分析可重入讀寫鎖的實(shí)現(xiàn)。讀寫鎖結(jié)構(gòu)分析繼承于,其中主要功能均在中完成,其中最重要功能為控制線程獲取鎖失敗后轉(zhuǎn)換為等待狀態(tài)及在滿足一定條件后喚醒等待狀態(tài)的線程。

概述

本文主要分析JCU包中讀寫鎖接口(ReadWriteLock)的重要實(shí)現(xiàn)類ReentrantReadWriteLock。主要實(shí)現(xiàn)讀共享,寫互斥功能,對(duì)比單純的互斥鎖在共享資源使用場(chǎng)景為頻繁讀取及少量修改的情況下可以較好的提高性能。

ReadWriteLock接口簡(jiǎn)單說明

ReadWriteLock接口只定義了兩個(gè)方法:

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

通過調(diào)用相應(yīng)方法獲取讀鎖或?qū)戞i,獲取的讀鎖及寫鎖都是Lock接口的實(shí)現(xiàn),可以如同使用Lock接口一樣使用(其實(shí)也有一些特性是不支持的)。

ReentrantReadWriteLock使用示例

讀寫鎖的使用并不復(fù)雜,可以參考以下使用示例:

   class RWDictionary {
    private final Map m = new TreeMap();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();
 
    public Data get(String key) {
      r.lock();
      try { return m.get(key); }
      finally { r.unlock(); }
    }
    public String[] allKeys() {
      r.lock();
      try { return m.keySet().toArray(); }
      finally { r.unlock(); }
    }
    public Data put(String key, Data value) {
      w.lock();
      try { return m.put(key, value); }
      finally { w.unlock(); }
    }
    public void clear() {
      w.lock();
      try { m.clear(); }
      finally { w.unlock(); }
    }
  }

與普通重入鎖使用的主要區(qū)別在于需要使用不同的鎖對(duì)象引用讀寫鎖,并且在讀寫時(shí)分別調(diào)用對(duì)應(yīng)的鎖。

ReentrantReadWriteLock鎖實(shí)現(xiàn)分析

本節(jié)通過學(xué)習(xí)源碼分析可重入讀寫鎖的實(shí)現(xiàn)。

圖解重要函數(shù)及對(duì)象關(guān)系

根據(jù)示例代碼可以發(fā)現(xiàn),讀寫鎖需要關(guān)注的重點(diǎn)函數(shù)為獲取讀鎖及寫鎖的函數(shù),對(duì)于讀鎖及寫鎖對(duì)象則主要關(guān)注加鎖和解鎖函數(shù),這幾個(gè)函數(shù)及對(duì)象關(guān)系如下圖:
從圖中可見讀寫鎖的加鎖解鎖操作最終都是調(diào)用ReentrantReadWriteLock類的內(nèi)部類Sync提供的方法。與{% post_link 細(xì)談重入鎖ReentrantLock %}一文中描述相似,Sync對(duì)象通過繼承AbstractQueuedSynchronizer進(jìn)行實(shí)現(xiàn),故后續(xù)分析主要基于Sync類進(jìn)行。

讀寫鎖Sync結(jié)構(gòu)分析

Sync繼承于AbstractQueuedSynchronizer,其中主要功能均在AbstractQueuedSynchronizer中完成,其中最重要功能為控制線程獲取鎖失敗后轉(zhuǎn)換為等待狀態(tài)及在滿足一定條件后喚醒等待狀態(tài)的線程。先對(duì)AbstractQueuedSynchronizer進(jìn)行觀察。

AbstractQueuedSynchronizer圖解

為了更好理解AbstractQueuedSynchronizer的運(yùn)行機(jī)制,可以首先研究其內(nèi)部數(shù)據(jù)結(jié)構(gòu),如下圖:
圖中展示AQS類較為重要的數(shù)據(jù)結(jié)構(gòu),包括int類型變量state用于記錄鎖的狀態(tài),繼承自AbstractOwnableSynchronizer類的Thread類型變量exclusiveOwnerThread用于指向當(dāng)前排他的獲取鎖的線程,AbstractQueuedSynchronizer.Node類型的變量headtail。
其中Node對(duì)象表示當(dāng)前等待鎖的節(jié)點(diǎn),Nodethread變量指向等待的線程,waitStatus表示當(dāng)前等待節(jié)點(diǎn)狀態(tài),mode為節(jié)點(diǎn)類型。多個(gè)節(jié)點(diǎn)之間使用prevnext組成雙向鏈表,參考CLH鎖隊(duì)列的方式進(jìn)行鎖的獲取,但其中與CLH隊(duì)列的重要區(qū)別在于CLH隊(duì)列中后續(xù)節(jié)點(diǎn)需要自旋輪詢前節(jié)點(diǎn)狀態(tài)以確定前置節(jié)點(diǎn)是否已經(jīng)釋放鎖,期間不釋放CPU資源,而AQSNode節(jié)點(diǎn)指向的線程在獲取鎖失敗后調(diào)用LockSupport.park函數(shù)使其進(jìn)入阻塞狀態(tài),讓出CPU資源,故在前置節(jié)點(diǎn)釋放鎖時(shí)需要調(diào)用unparkSuccessor函數(shù)喚醒后繼節(jié)點(diǎn)。
根據(jù)以上說明可得知此上圖圖主要表現(xiàn)當(dāng)前thread0線程獲取了鎖,thread1線程正在等待。

讀寫鎖Sync對(duì)于AQS使用

讀寫鎖中Sync類是繼承于AQS,并且主要使用上文介紹的數(shù)據(jù)結(jié)構(gòu)中的statewaitStatus變量進(jìn)行實(shí)現(xiàn)。
實(shí)現(xiàn)讀寫鎖與實(shí)現(xiàn)普通互斥鎖的主要區(qū)別在于需要分別記錄讀鎖狀態(tài)及寫鎖狀態(tài),并且等待隊(duì)列中需要區(qū)別處理兩種加鎖操作。
Sync使用state變量同時(shí)記錄讀鎖與寫鎖狀態(tài),將int類型的state變量分為高16位與第16位,高16位記錄讀鎖狀態(tài),低16位記錄寫鎖狀態(tài),如下圖所示:
Sync使用不同的mode描述等待隊(duì)列中的節(jié)點(diǎn)以區(qū)分讀鎖等待節(jié)點(diǎn)和寫鎖等待節(jié)點(diǎn)。mode取值包括SHAREDEXCLUSIVE兩種,分別代表當(dāng)前等待節(jié)點(diǎn)為讀鎖和寫鎖。

讀寫鎖Sync代碼過程分析 寫鎖加鎖

通過對(duì)于重要函數(shù)關(guān)系的分析,寫鎖加鎖最終調(diào)用Sync類的acquire函數(shù)(繼承自AQS

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

現(xiàn)在分情況圖解分析

無鎖狀態(tài)

無鎖狀態(tài)AQS內(nèi)部數(shù)據(jù)結(jié)構(gòu)如下圖所示:
其中state變量為0,表示高位地位地位均為0,沒有任何鎖,且等待節(jié)點(diǎn)的首尾均指向空(此處特指head節(jié)點(diǎn)沒有初始化時(shí)),鎖的所有者線程也為空。
在無鎖狀態(tài)進(jìn)行加鎖操作,線程調(diào)用acquire 函數(shù),首先使用tryAcquire函數(shù)判斷鎖是否可獲取成功,由于當(dāng)前是無鎖狀態(tài)必然成功獲取鎖(如果多個(gè)線程同時(shí)進(jìn)入此函數(shù),則有且只有一個(gè)線程可調(diào)用compareAndSetState成功,其他線程轉(zhuǎn)入獲取鎖失敗的流程)。獲取鎖成功后AQS狀態(tài)為:

有鎖狀態(tài)

在加寫鎖時(shí)如果當(dāng)前AQS已經(jīng)是有鎖狀態(tài),則需要進(jìn)一步處理。有鎖狀態(tài)主要分為已有寫鎖和已有讀鎖狀態(tài),并且根據(jù)最終當(dāng)前線程是否可直接獲取鎖分為兩種情況:

非重入:如果滿足一下兩個(gè)條件之一,當(dāng)前線程必須加入等待隊(duì)列(暫不考慮非公平鎖搶占情況)
a. 已有讀鎖;
b. 有寫鎖且獲取寫鎖的線程不為當(dāng)前請(qǐng)求鎖的線程。

重入:有寫鎖且當(dāng)前獲取寫鎖的線程與當(dāng)前請(qǐng)求鎖的線程為同一線程,則直接獲取鎖并將寫鎖狀態(tài)值加1。

寫鎖重入狀態(tài)如圖:
寫鎖非重入等待狀態(tài)如圖:
在非重入狀態(tài),當(dāng)前線程創(chuàng)建等待節(jié)點(diǎn)追加到等待隊(duì)列隊(duì)尾,如果當(dāng)前頭結(jié)點(diǎn)為空,則需要?jiǎng)?chuàng)建一個(gè)默認(rèn)的頭結(jié)點(diǎn)。
之后再當(dāng)前獲取鎖的線程釋放鎖后,會(huì)喚醒等待中的節(jié)點(diǎn),即為thread1。如果當(dāng)前等待隊(duì)列存在多個(gè)等待節(jié)點(diǎn),由于thread1等待節(jié)點(diǎn)為EXCLUSIVE模式,則只會(huì)喚醒當(dāng)前一個(gè)節(jié)點(diǎn),不會(huì)傳播喚醒信號(hào)。

讀鎖加鎖

通過對(duì)于重要函數(shù)關(guān)系的分析,寫鎖加鎖最終調(diào)用Sync類的acquireShared函數(shù)(繼承自AQS):

public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

同上文,現(xiàn)在分情況圖解分析

無鎖狀態(tài)

無所狀態(tài)AQS內(nèi)部數(shù)據(jù)狀態(tài)圖與寫加鎖是無鎖狀態(tài)一致:
在無鎖狀態(tài)進(jìn)行加鎖操作,線程調(diào)用acquireShared 函數(shù),首先使用tryAcquireShared函數(shù)判斷共享鎖是否可獲取成功,由于當(dāng)前為無鎖狀態(tài)則獲取鎖一定成功(如果同時(shí)多個(gè)線程在讀鎖進(jìn)行競(jìng)爭(zhēng),則只有一個(gè)線程能夠直接獲取讀鎖,其他線程需要進(jìn)入fullTryAcquireShared函數(shù)繼續(xù)進(jìn)行鎖的獲取,該函數(shù)在后文說明)。當(dāng)前線程獲取讀鎖成功后,AQS內(nèi)部結(jié)構(gòu)如圖所示:
其中有兩個(gè)新的變量:firstReaderfirstReaderHoldCount。firstReader指向在無鎖狀態(tài)下第一個(gè)獲取讀鎖的線程,firstReaderHoldCount記錄第一個(gè)獲取讀鎖的線程持有當(dāng)前鎖的計(jì)數(shù)(主要用于重入)。

有鎖狀態(tài)

無鎖狀態(tài)獲取讀鎖比較簡(jiǎn)單,在有鎖狀態(tài)則需要分情況討論。其中需要分當(dāng)前被持有的鎖是讀鎖還是寫鎖,并且每種情況需要區(qū)分等待隊(duì)列中是否有等待節(jié)點(diǎn)。

已有讀鎖且等待隊(duì)列為空

此狀態(tài)比較簡(jiǎn)單,圖示如:此時(shí)線程申請(qǐng)讀鎖,首先調(diào)用readerShouldBlock函數(shù)進(jìn)行判斷,該函數(shù)根據(jù)當(dāng)前鎖是否為公平鎖判斷規(guī)則稍有不同。如果為非公平鎖,則只需要當(dāng)前第一個(gè)等待節(jié)點(diǎn)不是寫鎖就可以嘗試獲取鎖(考慮第一點(diǎn)為寫鎖主要為了方式寫鎖“餓死”);如果是公平鎖則只要有等待節(jié)點(diǎn)且當(dāng)前鎖不為重入就需要等待。
由于本節(jié)的前提是等待隊(duì)列為空的情況,故readerShouldBlock函數(shù)一定返回false,則當(dāng)前線程使用CAS對(duì)讀鎖計(jì)數(shù)進(jìn)行增加(同上文,如果同時(shí)多個(gè)線程在讀鎖進(jìn)行競(jìng)爭(zhēng),則只有一個(gè)線程能夠直接獲取讀鎖,其他線程需要進(jìn)入fullTryAcquireShared函數(shù)繼續(xù)進(jìn)行鎖的獲?。?。
在成功對(duì)讀鎖計(jì)數(shù)器進(jìn)行增加后,當(dāng)前線程需要繼續(xù)對(duì)當(dāng)前線程持有讀鎖的計(jì)數(shù)進(jìn)行增加。此時(shí)分為兩種情況:

當(dāng)前線程是第一個(gè)獲取讀鎖的線程,此時(shí)由于第一個(gè)獲取讀鎖的線程已經(jīng)通過firstReaderfirstReaderHoldCount兩個(gè)變量進(jìn)行存儲(chǔ),則僅僅需要將firstReaderHoldCount加1即可;

當(dāng)前線程不是第一個(gè)獲取讀鎖的線程,則需要使用readHolds進(jìn)行存儲(chǔ),readHoldsThreadLocal的子類,通過readHolds可獲取當(dāng)前線程對(duì)應(yīng)的HoldCounter類的對(duì)象,該對(duì)象保存了當(dāng)前線程獲取讀鎖的計(jì)數(shù)。考慮程序的局部性原理,又使用cachedHoldCounter緩存最近使用的HoldCounter類的對(duì)象,如在一段時(shí)間內(nèi)只有一個(gè)線程請(qǐng)求讀鎖則可加速對(duì)讀鎖獲取的計(jì)數(shù)。

第一個(gè)讀鎖線程重入如圖:
非首節(jié)點(diǎn)獲取讀鎖
根據(jù)上圖所示,thread0為首節(jié)點(diǎn),thread1線程繼續(xù)申請(qǐng)讀鎖,獲取成功后使用ThreadLocal鏈接的方式進(jìn)行存儲(chǔ)計(jì)數(shù)對(duì)象,并且由于其為最近獲取讀鎖的線程,則cachedHoldCounter對(duì)象設(shè)置指向thread1對(duì)應(yīng)的計(jì)數(shù)對(duì)象。

已有讀鎖且等待隊(duì)列不為空

在當(dāng)前鎖已經(jīng)被讀鎖獲取,且等待隊(duì)列不為空的情況下 ,可知等待隊(duì)列的頭結(jié)點(diǎn)一定為寫鎖獲取等待,這是由于在讀寫鎖實(shí)現(xiàn)過程中,如果某線程獲取了讀鎖,則會(huì)喚醒當(dāng)前等到節(jié)點(diǎn)之后的所有等待模式為SHARED的節(jié)點(diǎn),直到隊(duì)尾或遇到EXCLUSIVE模式的等待節(jié)點(diǎn)(具體實(shí)現(xiàn)函數(shù)為setHeadAndPropagate后續(xù)還會(huì)遇到)。所以可以確定當(dāng)前為讀鎖狀態(tài)其有等待節(jié)點(diǎn)情況下,首節(jié)點(diǎn)一定是寫鎖等待。如圖所示:
上圖展示當(dāng)前thread0thread1線程獲取讀鎖,thread0為首個(gè)獲取讀鎖的節(jié)點(diǎn),并且thread2線程在等待獲取寫鎖。
在上圖顯示的狀態(tài)下,無論公平鎖還是非公平鎖的實(shí)現(xiàn),新的讀鎖加鎖一定會(huì)進(jìn)行排隊(duì),添加等待節(jié)點(diǎn)在寫鎖等待節(jié)點(diǎn)之后,這樣可以防止寫操作的餓死。申請(qǐng)讀鎖后的狀態(tài)如圖所示:
如圖所示,在當(dāng)前鎖被為讀鎖且有等待隊(duì)列情況下,thread3thread4線程申請(qǐng)讀鎖,則被封裝為等待節(jié)點(diǎn)追加到當(dāng)前等待隊(duì)列后,節(jié)點(diǎn)模式為SHARED,線程使用LockSupport.park函數(shù)進(jìn)入阻塞狀態(tài),讓出CPU資源,直到前驅(qū)的等待節(jié)點(diǎn)完成鎖的獲取和釋放后進(jìn)行喚醒。

已有寫鎖被獲取

當(dāng)前線程申請(qǐng)讀鎖時(shí)發(fā)現(xiàn)寫鎖已經(jīng)被獲取,則無論等待隊(duì)列是否為空,線程一定會(huì)需要加入等待隊(duì)列(注意在非公平鎖實(shí)現(xiàn)且前序沒有寫鎖申請(qǐng)的等待,線程有機(jī)會(huì)搶占獲取鎖而不進(jìn)入等待隊(duì)列)。寫鎖被獲取的情況下,AQS狀態(tài)為如下狀態(tài)
在兩種情況下,讀鎖獲取都會(huì)進(jìn)入等待隊(duì)列等待前序節(jié)點(diǎn)喚醒,這里不再贅述。

讀等待節(jié)點(diǎn)被喚醒

讀寫鎖與單純的排他鎖主要區(qū)別在于讀鎖的共享性,在讀寫鎖實(shí)現(xiàn)中保證讀鎖能夠共享的其中一個(gè)機(jī)制就在于,如果一個(gè)讀鎖等待節(jié)點(diǎn)被喚醒后其會(huì)繼續(xù)喚醒拍在當(dāng)前喚醒節(jié)點(diǎn)之后的SHARED模式等待節(jié)點(diǎn)。查看源碼:

 private void doAcquireShared(int arg) {
       final Node node = addWaiter(Node.SHARED);
       boolean failed = true;
       try {
           boolean interrupted = false;
           for (;;) {
               final Node p = node.predecessor();
               if (p == head) {
                   int r = tryAcquireShared(arg);
                   if (r >= 0) {
                     //注意看這里
                       setHeadAndPropagate(node, r);
                       p.next = null; // help GC
                       if (interrupted)
                           selfInterrupt();
                       failed = false;
                       return;
                   }
               }
               if (shouldParkAfterFailedAcquire(p, node) &&
                   parkAndCheckInterrupt())
                   interrupted = true;
           }
       } finally {
           if (failed)
               cancelAcquire(node);
       }
   }

for循環(huán)中,線程如果獲取讀鎖成功后,需要調(diào)用setHeadAndPropagate方法。查看其源碼:

private void setHeadAndPropagate(Node node, int propagate) {
       Node h = head; // Record old head for check below
       setHead(node);
       if (propagate > 0 || h == null || h.waitStatus < 0 ||
           (h = head) == null || h.waitStatus < 0) {
           Node s = node.next;
           if (s == null || s.isShared())
               doReleaseShared();
       }
   }

在滿足傳播條件情況下,獲取讀鎖后繼續(xù)喚醒后續(xù)節(jié)點(diǎn),所以如果當(dāng)前鎖是讀鎖狀態(tài)則等待節(jié)點(diǎn)第一個(gè)節(jié)點(diǎn)一定是寫鎖等待節(jié)點(diǎn)。

鎖降級(jí)

鎖降級(jí)算是獲取讀鎖的特例,如在t0線程已經(jīng)獲取寫鎖的情況下,再調(diào)取讀鎖加鎖函數(shù)則可以直接獲取讀鎖,但此時(shí)其他線程仍然無法獲取讀鎖或?qū)戞i,在t0線程釋放寫鎖后,如果有節(jié)點(diǎn)等待則會(huì)喚醒后續(xù)節(jié)點(diǎn),后續(xù)節(jié)點(diǎn)可見的狀態(tài)為目前有t0線程獲取了讀鎖。
所降級(jí)有什么應(yīng)用場(chǎng)景呢?引用讀寫鎖中使用示例代碼

   class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

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

其中針對(duì)變量cacheValid的使用主要過程為加讀鎖、讀取、釋放讀鎖、加寫鎖、修改值、加讀鎖、釋放寫鎖、使用數(shù)據(jù)、釋放讀鎖。其中后續(xù)幾步(加寫鎖、修改值、加讀鎖、釋放寫鎖、使用數(shù)據(jù)、釋放讀鎖)為典型的鎖降級(jí)。如果不使用鎖降級(jí),則過程可能有三種情況:

第一種:加寫鎖、修改值、釋放寫鎖、使用數(shù)據(jù),即使用寫鎖修改數(shù)據(jù)后直接使用剛修改的數(shù)據(jù),這樣可能有數(shù)據(jù)的不一致,如當(dāng)前線程釋放寫鎖的同時(shí)其他線程(如t0)獲取寫鎖準(zhǔn)備修改(還沒有改)cacheValid變量,而當(dāng)前線程卻繼續(xù)運(yùn)行,則當(dāng)前線程讀到的cacheValid變量的值為t0修改前的老數(shù)據(jù);

第二種:加寫鎖、修改值、使用數(shù)據(jù)、釋放寫鎖,即將修改數(shù)據(jù)與再次使用數(shù)據(jù)合二為一,這樣不會(huì)有數(shù)據(jù)的不一致,但是由于混用了讀寫兩個(gè)過程,以排它鎖的方式使用讀寫鎖,減弱了讀寫鎖讀共享的優(yōu)勢(shì),增加了寫鎖(獨(dú)占鎖)的占用時(shí)間;

第三種:加寫鎖、修改值、釋放寫鎖、加讀鎖、使用數(shù)據(jù)、釋放讀鎖,即使用寫鎖修改數(shù)據(jù)后再請(qǐng)求讀鎖來使用數(shù)據(jù),這是時(shí)數(shù)據(jù)的一致性是可以得到保證的,但是由于釋放寫鎖和獲取讀鎖之間存在時(shí)間差,則當(dāng)前想成可能會(huì)需要進(jìn)入等待隊(duì)列進(jìn)行等待,可能造成線程的阻塞降低吞吐量。

因此針對(duì)以上情況提供了鎖的降級(jí)功能,可以在完成數(shù)據(jù)修改后盡快讀取最新的值,且能夠減少寫鎖占用時(shí)間。
最后注意,讀寫鎖不支持鎖升級(jí),即獲取讀鎖、讀數(shù)據(jù)、獲取寫鎖、釋放讀鎖、釋放寫鎖這個(gè)過程,因?yàn)樽x鎖為共享鎖,如同時(shí)有多個(gè)線程獲取了讀鎖后有一個(gè)線程進(jìn)行鎖升級(jí)獲取了寫鎖,這會(huì)造成同時(shí)有讀鎖(其他線程)和寫鎖的情況,造成其他線程可能無法感知新修改的數(shù)據(jù)(此為邏輯性錯(cuò)誤),并且在JAVA讀寫鎖實(shí)現(xiàn)上由于當(dāng)前線程獲取了讀鎖,再次請(qǐng)求寫鎖時(shí)必然會(huì)阻塞而導(dǎo)致后續(xù)釋放讀鎖的方法無法執(zhí)行,這回造成死鎖(此為功能性錯(cuò)誤)。

寫鎖釋放鎖過程

了解了加鎖過程后解鎖過程就非常簡(jiǎn)單,每次調(diào)用解鎖方法都會(huì)減少重入計(jì)數(shù)次數(shù),直到減為0則喚醒后續(xù)第一個(gè)等待節(jié)點(diǎn),如喚醒的后續(xù)節(jié)點(diǎn)為讀等待節(jié)點(diǎn),則后續(xù)節(jié)點(diǎn)會(huì)繼續(xù)傳播喚醒狀態(tài)。

讀鎖釋放過程

讀鎖釋放過比寫鎖稍微復(fù)雜,因?yàn)槭枪蚕礞i,所以可能會(huì)有多個(gè)線程同時(shí)獲取讀鎖,故在解鎖時(shí)需要做兩件事:

獲取當(dāng)前線程對(duì)應(yīng)的重入計(jì)數(shù),并進(jìn)行減1,此處天生為線程安全的,不需要特殊處理;

當(dāng)前讀鎖獲取次數(shù)減1,此處由于可能存在多線程競(jìng)爭(zhēng),故使用自旋CAS進(jìn)行設(shè)置。

完成以上兩步后,如讀狀態(tài)為0,則喚醒后續(xù)等待節(jié)點(diǎn)。

總結(jié)

根據(jù)以上分析,本文主要展示了讀寫鎖的場(chǎng)景及方式,并分析讀寫鎖核心功能(加解鎖)的代碼實(shí)現(xiàn)。Java讀寫鎖同時(shí)附帶了更多其他方法,包括鎖狀態(tài)監(jiān)控和帶超時(shí)機(jī)制的加鎖方法等,本文不在贅述。并且讀寫鎖中寫鎖可使用Conditon機(jī)制也不在詳細(xì)說明。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/76508.html

相關(guān)文章

  • Java多線程進(jìn)階(十)—— J.U.C之locks框架:基于AQS的讀寫鎖(5)

    摘要:關(guān)于,最后有兩點(diǎn)規(guī)律需要注意當(dāng)?shù)牡却?duì)列隊(duì)首結(jié)點(diǎn)是共享結(jié)點(diǎn),說明當(dāng)前寫鎖被占用,當(dāng)寫鎖釋放時(shí),會(huì)以傳播的方式喚醒頭結(jié)點(diǎn)之后緊鄰的各個(gè)共享結(jié)點(diǎn)。當(dāng)?shù)牡却?duì)列隊(duì)首結(jié)點(diǎn)是獨(dú)占結(jié)點(diǎn),說明當(dāng)前讀鎖被使用,當(dāng)讀鎖釋放歸零后,會(huì)喚醒隊(duì)首的獨(dú)占結(jié)點(diǎn)。 showImg(https://segmentfault.com/img/remote/1460000016012293); 本文首發(fā)于一世流云的專欄:...

    dunizb 評(píng)論0 收藏0
  • J.U.C|讀-寫鎖ReentrantReadWriteLock

    摘要:所以就有了讀寫鎖。只要沒有,讀取鎖可以由多個(gè)線程同時(shí)保持。其讀寫鎖為兩個(gè)內(nèi)部類都實(shí)現(xiàn)了接口。讀寫鎖同樣依賴自定義同步器來實(shí)現(xiàn)同步狀態(tài)的,而讀寫狀態(tài)就是其自定義同步器的狀態(tài)。判斷申請(qǐng)寫鎖數(shù)量是否超標(biāo)超標(biāo)則直接異常,反之則設(shè)置共享狀態(tài)。 一、寫在前面 在上篇我們聊到了可重入鎖(排它鎖)ReentrantLcok ,具體參見《J.U.C|可重入鎖ReentrantLock》 Reentra...

    Tonny 評(píng)論0 收藏0
  • J.U.C|讀-寫鎖ReentrantReadWriteLock

    摘要:所以就有了讀寫鎖。只要沒有,讀取鎖可以由多個(gè)線程同時(shí)保持。其讀寫鎖為兩個(gè)內(nèi)部類都實(shí)現(xiàn)了接口。讀寫鎖同樣依賴自定義同步器來實(shí)現(xiàn)同步狀態(tài)的,而讀寫狀態(tài)就是其自定義同步器的狀態(tài)。判斷申請(qǐng)寫鎖數(shù)量是否超標(biāo)超標(biāo)則直接異常,反之則設(shè)置共享狀態(tài)。 一、寫在前面 在上篇我們聊到了可重入鎖(排它鎖)ReentrantLcok ,具體參見《J.U.C|可重入鎖ReentrantLock》 Reentra...

    wendux 評(píng)論0 收藏0
  • ReentrantReadWriteLock

    摘要:類顧名思義是一種讀寫鎖它是接口的直接實(shí)現(xiàn)該類在內(nèi)部實(shí)現(xiàn)了具體獨(dú)占鎖特點(diǎn)的寫鎖以及具有共享鎖特點(diǎn)的讀鎖和一樣類也是通過定義內(nèi)部類實(shí)現(xiàn)框架的來實(shí)現(xiàn)獨(dú)占共享的功能屬于排他鎖這些鎖在同一時(shí)刻只允許一個(gè)線程進(jìn)行訪問但是在大多數(shù)場(chǎng)景下大部分時(shí)間都是提供 ReentrantReadWriteLock 類, 顧名思義, 是一種讀寫鎖, 它是 ReadWriteLock 接口的直接實(shí)現(xiàn), 該類在內(nèi)部實(shí)現(xiàn)...

    zsirfs 評(píng)論0 收藏0
  • Java多線程進(jìn)階(四)—— J.U.C之locks框架:ReentrantReadWriteLoc

    摘要:我們知道,的作用其實(shí)是對(duì)類的和的增強(qiáng),是為了讓線程在指定對(duì)象上等待,是一種線程之間進(jìn)行協(xié)調(diào)的工具。當(dāng)線程調(diào)用對(duì)象的方法時(shí),必須拿到和這個(gè)對(duì)象關(guān)聯(lián)的鎖。 showImg(https://segmentfault.com/img/remote/1460000016012566); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... 一、Reentr...

    kumfo 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

nemo

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<