概述本文主要分析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)型的變量head 及tail 。 其中Node 對(duì)象表示當(dāng)前等待鎖的節(jié)點(diǎn),Node 中thread 變量指向等待的線(xiàn)程,waitStatus 表示當(dāng)前等待節(jié)點(diǎn)狀態(tài),mode 為節(jié)點(diǎn)類(lèi)型。多個(gè)節(jié)點(diǎn)之間使用prev 及next 組成雙向鏈表,參考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資源,而AQS 中Node 節(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)中的state 及waitStatus 變量進(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 取值包括SHARED 及EXCLUSIVE 兩種,分別代表當(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)程是否可直接獲取鎖分為兩種情況: - 非重入:如果滿(mǎn)足一下兩個(gè)條件之一,當(dāng)前線(xiàn)程必須加入等待隊(duì)列(暫不考慮非公平鎖搶占情況)
a. 已有讀鎖; b. 有寫(xiě)鎖且獲取寫(xiě)鎖的線(xiàn)程不為當(dāng)前請(qǐng)求鎖的線(xiàn)程。 - 重入:有寫(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è)新的變量:firstReader 及firstReaderHoldCount 。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í)分為兩種情況: - 當(dāng)前線(xiàn)程是第一個(gè)獲取讀鎖的線(xiàn)程,此時(shí)由于第一個(gè)獲取讀鎖的線(xiàn)程已經(jīng)通過(guò)
firstReader 及firstReaderHoldCount 兩個(gè)變量進(jìn)行存儲(chǔ),則僅僅需要將firstReaderHoldCount 加1即可; - 當(dāng)前線(xiàn)程不是第一個(gè)獲取讀鎖的線(xiàn)程,則需要使用
readHolds 進(jìn)行存儲(chǔ),readHolds 是ThreadLocal 的子類(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)前thread0 與thread1 線(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ì)列情況下,thread3 及thread4 線(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í)需要做兩件事: - 獲取當(dāng)前線(xiàn)程對(duì)應(yīng)的重入計(jì)數(shù),并進(jìn)行減1,此處天生為線(xiàn)程安全的,不需要特殊處理;
- 當(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ō)明。
|