2020国产成人精品视频,性做久久久久久久久,亚洲国产成人久久综合一区,亚洲影院天堂中文av色

分享

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

 沙門(mén)空海 2020-03-24

概述

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

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

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

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

ReentrantReadWriteLock使用示例

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

   class RWDictionary {    private final Map<String, Data> m = new TreeMap<String, Data>();    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ì)象引用讀寫(xiě)鎖,并且在讀寫(xiě)時(shí)分別調(diào)用對(duì)應(yīng)的鎖。

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

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

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

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

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

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

AbstractQueuedSynchronizer圖解

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

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

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

讀寫(xiě)鎖Sync代碼過(guò)程分析

寫(xiě)鎖加鎖

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

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

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

無(wú)鎖狀態(tài)

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

有鎖狀態(tài)

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

  1. 非重入:如果滿(mǎn)足一下兩個(gè)條件之一,當(dāng)前線(xiàn)程必須加入等待隊(duì)列(暫不考慮非公平鎖搶占情況)
    a. 已有讀鎖;
    b. 有寫(xiě)鎖且獲取寫(xiě)鎖的線(xiàn)程不為當(dāng)前請(qǐng)求鎖的線(xiàn)程。
  2. 重入:有寫(xiě)鎖且當(dāng)前獲取寫(xiě)鎖的線(xiàn)程與當(dāng)前請(qǐng)求鎖的線(xiàn)程為同一線(xiàn)程,則直接獲取鎖并將寫(xiě)鎖狀態(tài)值加1。

寫(xiě)鎖重入狀態(tài)如圖:
寫(xiě)鎖非重入等待狀態(tài)如圖:
在非重入狀態(tài),當(dāng)前線(xiàn)程創(chuàng)建等待節(jié)點(diǎn)追加到等待隊(duì)列隊(duì)尾,如果當(dāng)前頭結(jié)點(diǎn)為空,則需要?jiǎng)?chuàng)建一個(gè)默認(rèn)的頭結(jié)點(diǎn)。
之后再當(dāng)前獲取鎖的線(xiàn)程釋放鎖后,會(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)。

讀鎖加鎖

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

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

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

無(wú)鎖狀態(tài)

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

有鎖狀態(tài)

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

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

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

  1. 當(dāng)前線(xiàn)程是第一個(gè)獲取讀鎖的線(xiàn)程,此時(shí)由于第一個(gè)獲取讀鎖的線(xiàn)程已經(jīng)通過(guò)firstReaderfirstReaderHoldCount兩個(gè)變量進(jìn)行存儲(chǔ),則僅僅需要將firstReaderHoldCount加1即可;
  2. 當(dāng)前線(xiàn)程不是第一個(gè)獲取讀鎖的線(xiàn)程,則需要使用readHolds進(jìn)行存儲(chǔ),readHoldsThreadLocal的子類(lèi),通過(guò)readHolds可獲取當(dāng)前線(xiàn)程對(duì)應(yīng)的HoldCounter類(lèi)的對(duì)象,該對(duì)象保存了當(dāng)前線(xiàn)程獲取讀鎖的計(jì)數(shù)??紤]程序的局部性原理,又使用cachedHoldCounter緩存最近使用的HoldCounter類(lèi)的對(duì)象,如在一段時(shí)間內(nèi)只有一個(gè)線(xiàn)程請(qǐng)求讀鎖則可加速對(duì)讀鎖獲取的計(jì)數(shù)。

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

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

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

已有寫(xiě)鎖被獲取

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

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

讀寫(xiě)鎖與單純的排他鎖主要區(qū)別在于讀鎖的共享性,在讀寫(xiě)鎖實(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)中,線(xià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();       }   }

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

鎖降級(jí)

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

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的使用主要過(guò)程為加讀鎖、讀取、釋放讀鎖、加寫(xiě)鎖、修改值、加讀鎖、釋放寫(xiě)鎖、使用數(shù)據(jù)、釋放讀鎖。其中后續(xù)幾步(加寫(xiě)鎖、修改值、加讀鎖、釋放寫(xiě)鎖、使用數(shù)據(jù)、釋放讀鎖)為典型的鎖降級(jí)。如果不使用鎖降級(jí),則過(guò)程可能有三種情況:

  • 第一種:加寫(xiě)鎖、修改值、釋放寫(xiě)鎖、使用數(shù)據(jù),即使用寫(xiě)鎖修改數(shù)據(jù)后直接使用剛修改的數(shù)據(jù),這樣可能有數(shù)據(jù)的不一致,如當(dāng)前線(xiàn)程釋放寫(xiě)鎖的同時(shí)其他線(xiàn)程(如t0)獲取寫(xiě)鎖準(zhǔn)備修改(還沒(méi)有改)cacheValid變量,而當(dāng)前線(xiàn)程卻繼續(xù)運(yùn)行,則當(dāng)前線(xiàn)程讀到的cacheValid變量的值為t0修改前的老數(shù)據(jù);
  • 第二種:加寫(xiě)鎖、修改值、使用數(shù)據(jù)、釋放寫(xiě)鎖,即將修改數(shù)據(jù)與再次使用數(shù)據(jù)合二為一,這樣不會(huì)有數(shù)據(jù)的不一致,但是由于混用了讀寫(xiě)兩個(gè)過(guò)程,以排它鎖的方式使用讀寫(xiě)鎖,減弱了讀寫(xiě)鎖讀共享的優(yōu)勢(shì),增加了寫(xiě)鎖(獨(dú)占鎖)的占用時(shí)間;
  • 第三種:加寫(xiě)鎖、修改值、釋放寫(xiě)鎖、加讀鎖、使用數(shù)據(jù)、釋放讀鎖,即使用寫(xiě)鎖修改數(shù)據(jù)后再請(qǐng)求讀鎖來(lái)使用數(shù)據(jù),這是時(shí)數(shù)據(jù)的一致性是可以得到保證的,但是由于釋放寫(xiě)鎖和獲取讀鎖之間存在時(shí)間差,則當(dāng)前想成可能會(huì)需要進(jìn)入等待隊(duì)列進(jìn)行等待,可能造成線(xiàn)程的阻塞降低吞吐量。

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

寫(xiě)鎖釋放鎖過(guò)程

了解了加鎖過(guò)程后解鎖過(guò)程就非常簡(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)。

讀鎖釋放過(guò)程

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

  1. 獲取當(dāng)前線(xiàn)程對(duì)應(yīng)的重入計(jì)數(shù),并進(jìn)行減1,此處天生為線(xiàn)程安全的,不需要特殊處理;
  2. 當(dāng)前讀鎖獲取次數(shù)減1,此處由于可能存在多線(xiàn)程競(jìng)爭(zhēng),故使用自旋CAS進(jìn)行設(shè)置。

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

總結(jié)

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

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀(guān)點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多