在多線程環境下,爲了保證線程安全, 咱們一般會對共享資源加鎖操做,咱們經常使用Synchronized關鍵字或者ReentrantLock 來實現,這二者加鎖方式都是排他鎖,即同一時刻最多容許一個線程操做,然而大多數場景中對共享資源讀多於寫,那麼存在線程安全問題的是寫操做(修改,添加,刪除),咱們是否應該考慮將讀和寫兩個分開,只要運用合理,併發性能是否是能夠提升,吞吐量增大呢? ReentrantReadWriteLock已經爲咱們實現了這種機制,咱們一塊兒來看它是怎樣實現的吧!html
在查看可重入讀寫鎖的源碼前,有幾個概念須要先知道,對於後面理解源碼頗有幫助。java
一、ReentrantReadWriteLock 內部 Sync類依然是繼承AQS實現的,所以同步狀態字段 state,依然表示對鎖資源的佔用狀況。那麼如何實現一個 int類型的state 同時來表示讀寫鎖兩種狀態的佔用狀況呢? 這裏實現很是巧妙,將4個字節的int類型, 32位拆分爲2部分,高16位表示讀鎖的佔用狀況,低16位表示寫鎖的佔用狀況,這樣讀寫鎖互不影響,相互獨立;也所以讀寫鎖的最大值是2^16-1 = 65535,不能超過16位,下面源碼有體現。node
state值表示如圖所示:緩存
二、讀鎖是共享鎖,只要不超過最大值,可多個線程同時獲取; 寫鎖是排他鎖,同一時刻最多容許一個線程獲取。安全
寫鎖與其餘鎖都互斥,含寫寫互斥,寫讀互斥,讀寫互斥。多線程
三、state可同時表示讀寫鎖的狀態,state的高16位表示獲取讀鎖的線程數,讀鎖支持可重入,即一個線程也可屢次獲取讀鎖,怎麼維護每一個讀鎖線程的重入次數的? 每一個線程有一個計數器 HoldCounter,用ThreadLocal來存放每一個線程的計數器;state的低16位表示寫鎖的同步狀態,由於寫鎖是排他鎖,這裏就不能表示獲取寫鎖的線程數了,只能表示寫鎖的重入次數,獲取寫鎖的線程可屢次重複獲取寫鎖(支持重入)。併發
讀鎖的計數器的實現原理以下:oop
可見ThreadLocalHoldCounter繼承 ThreadLocal,每一個獲取讀鎖的線程是經過其本地變量來存儲本身的計數器,來統計獲取讀鎖的重入次數。ThreadLocal原理解析性能
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { //重寫了ThreadLocal的initialValue方法 public HoldCounter initialValue() { return new HoldCounter(); } }
四、state的高16位須要記錄獲取讀鎖的線程數,每增長一個線程獲取讀鎖,在state的高16執行加1操做,即state+2^16,寫鎖增長重入次數,直接 state+1便可。測試
五、鎖降級:獲取寫鎖的線程,能夠再次獲取到讀鎖,即寫鎖降級爲讀鎖。
讀鎖能夠升級爲寫鎖嗎? 不能夠,由於存在線程安全問題,試想獲取讀鎖的線程有多個,其中一個線程升級爲寫鎖,對臨界區資源進行操做,好比修改了某個值,對其餘已經獲取讀鎖的線程不可見,出現線程安全問題。
代碼演示:
一、讀寫狀態
AQS(AbstractQueuedSynchronizer的簡稱)中同步狀態字段 private volatile int state, int類型,4個字節,32位,拆分爲高16位表示讀狀態,低16位表示寫狀態,以下定義了一些常量,實現獲取讀寫鎖的數量。
ReentrantReadWriteLock部分代碼以下:
//分隔位數,16位 static final int SHARED_SHIFT = 16; //讀鎖加1的數量,1左位移16位, (16)0x10000 = (2)1000000000000000= (10) 65536 static final int SHARED_UNIT = (1 << SHARED_SHIFT); //讀寫鎖的最大數量, (16)0xFFFFFFFF =(2)1111111111111111 =(10)65535 static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; //寫鎖的掩碼,用於計算寫鎖重入次數時,將state的高16所有置爲0, 等於(2)1111111111111111 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //獲取讀鎖數,表示當前有多少個線程獲取到讀鎖 static int sharedCount(int c) { return c >>> SHARED_SHIFT; } //獲取寫鎖重入次數(不等於0表示有線程持有獨佔鎖,大於1,表示寫鎖有重入) static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
分別看一下獲取讀寫鎖數量的方法。
獲取佔用讀鎖的線程數,代碼以下:
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
傳入的c爲 state,state 無符號右移16位,抹去低16位值,左邊補0
示例圖以下:
獲取寫鎖的值的方法
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
與運算,將高16所有置爲0,低16值表明寫鎖的值,&運算,相同爲1,不一樣爲0,獲得低16位寫鎖值。
示例圖以下:
二、三個鎖概念
如下分析三種狀況下state,r, w 的值及表明的含義:
state =1, w =1, r =0
獲取寫鎖加1操做就比較簡單了,由於寫鎖是獨佔鎖,與正常的ReentrantLock獲取鎖實現同樣,佔用state的低16位表示,不用看state的高16,左邊補16位0。獲取寫鎖一次,直接 c+1;
state =65536, w= 0, r=1
c初始爲0 ,獲取讀鎖,則讀鎖數量+1,執行 c + SHARED_UNIT, SHARED_UNIT = (2)1000000000000000 = (10)65536,括號內表示進制,SHARED_UNIT是每次讀鎖加1的數值。
以下圖所示: 在獲取讀鎖數量 r時,將state的低16位抹去,r=1,而state此時的值= 2^16 =65536,state的實際值可能會很大,但其實分別拆分讀寫鎖的值不必定大,只是讀鎖值表示在高位,會形成state值很大。
state = 65537,w=1, r=1
state二進制表示: 00000000 00000001 00000000 00000001
鎖降級代碼演示以下:
package readwritelock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @author zdd * 2019/12/30 上午 * Description: 鎖降級測試 */ public class ReadWriteLockTest { static Integer shareVar = 0; public static void main(String[] args) { ReentrantReadWriteLock rw = new ReentrantReadWriteLock(); //1,首先獲取寫鎖 rw.writeLock().lock(); //2.修改共享變量值 shareVar = 10 ; //3.再獲取讀鎖 rw.readLock().lock(); System.out.println("讀取變量值 shareVar:"+ shareVar); //4.釋放寫鎖 rw.writeLock().unlock(); //5.釋放讀鎖 rw.readLock().unlock(); } }
ReentrantReadWriteLock 類中有ReadLock和WriteLock,分別對應讀鎖和寫鎖,而讀寫鎖又分爲公平方式和非公平方式獲取鎖。
簡略類圖結構以下:
構造方法以下:根據傳入參數設置公平或者非公平獲取鎖方式,默認是非公平方式
public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); }
因爲寫鎖是獨佔鎖,因爲寫鎖是獨佔鎖,獲取寫鎖的方式在AQS中已經說過了,詳見AQS源代碼分析, 只是每一個子類的嘗試獲取鎖方式不一樣,因此ReentrantReadWriteLock類獲取寫鎖過程就看一下嘗試獲取鎖方法的源碼。
tryAcquire(int acquires),獲取鎖失敗則加入同步隊列中等待獲取鎖,源代碼以下:
protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); //1,獲取同步狀態state的值,注意該值可表示讀寫鎖的同步狀態 int c = getState(); //2,獲取寫鎖狀態,低16位的值 int w = exclusiveCount(c); //3,若是同步鎖狀態不爲0,有線程已經獲取到了鎖 if (c != 0) { //4,w==0則表示寫鎖爲0,那麼必定有線程獲取了讀鎖,須要等待,讀寫互斥 //current != getExclusiveOwnerThread() 當前線程不等於已經獲取到寫鎖的線程,則也需等待其釋放,寫寫互斥 if (w == 0 || current != getExclusiveOwnerThread()) return false; //5,此時再次獲取鎖,判斷鎖重入次數是否超過最大限定次數 if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); //更新寫鎖重入次數 setState(c + acquires); return true; } //6,代碼執行這,必定是c==0,同步鎖空閒狀況 //writerShouldBlock該方法是基於公平鎖和非公平鎖2種方式的體現 if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; //獲取到鎖,設置獨佔鎖爲當前寫鎖線程 setExclusiveOwnerThread(current); return true; }
寫鎖是否應該阻塞等待
final boolean writerShouldBlock() { //直接返回false return false; // writers can always barge }
須要判斷同步隊列中是否還有其餘線程在掛起等待,如存在應該按照入隊順序獲取鎖
final boolean writerShouldBlock() { return hasQueuedPredecessors(); }
public final boolean hasQueuedPredecessors() { //1.獲取同步隊列頭,尾節點 Node t = tail; Node h = head; Node s; // h !=t 同步隊列不爲空 // 隊列中還有其餘線程在等待鎖,則返回true return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
unlock方法釋放鎖
public void unlock() { sync.release(1); }
可見,調用內部類Sync的release方法,Sync繼承AQS
public final boolean release(int arg) { if (tryRelease(arg)) { //1,釋放鎖成功 Node h = head; if (h != null && h.waitStatus != 0) //2.喚醒同步隊列中等待線程 unparkSuccessor(h); return true; } return false; }
核心在嘗試釋放鎖方法上,看看寫鎖的釋放鎖方法tryRelease
protected final boolean tryRelease(int releases) { //1,判斷當前線程是否持有當前鎖 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); //2,同步狀態 - 須要釋放的寫鎖同步值 int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0; if (free) //3,free ==true,徹底釋放寫鎖,將當前獲取獨佔鎖線程置空 setExclusiveOwnerThread(null); //4,更新state值 setState(nextc); return free; }
注: 在釋放寫鎖佔用次數時, state的高16的讀鎖有值也不影響,減去releases,首先減去的state低位的數,並且在釋放寫鎖時,state的低16位的值必定>=1,不存在減小讀鎖的值狀況。
int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0;
也可改寫爲以下面代碼
//1,獲取state值 int c = getState(); //2,獲取寫鎖的值 int w= exclusiveCount(c); int remain = w- releases; boolean free = remain== 0;
讀鎖調用lock方法加鎖,實際調用Sync的acquireShared方法
public void lock() { sync.acquireShared(1); }
走進acquireShared,獲取共享鎖方法
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
嘗試獲取鎖tryAcquireShared,若是返回值<0, 表示獲取讀鎖失敗
主要執行步驟:
一、首先判斷是否存在其餘線程在佔用寫鎖,有須要掛起等待;
二、在不用阻塞等待,且讀鎖值沒有超過最大值,cas更新成功了state的值,能夠獲取到讀鎖,還會作如下事:
a. 第一個獲取讀鎖的,直接記錄線程對象和其重入獲取讀鎖的次數
b. 非第一個獲取讀鎖的,則獲取緩存計數器(cachedHoldCounter),其記錄上一次獲取讀鎖的線程,若是是同一個線程,則直接更新其計數器的重入次數,若是緩存計數器爲空或緩存計數器的線程不是當前獲取讀鎖的線程,則從當前線程本地變量中獲取本身的計數器,更新計數器的值
protected final int tryAcquireShared(int unused) { //1,獲取當前線程對象 Thread current = Thread.currentThread(); //2,獲取同步鎖的值 int c = getState(); /*3,exclusiveCount(c) != 0 計算寫鎖的同步狀態,不等於0,說明有寫鎖已經獲取到同步鎖, *須要判斷當前線程是否等於獲取寫鎖線程, *是,能夠容許再次獲取讀鎖,這裏涉及到鎖降級問題,寫鎖能夠降爲讀鎖 *不然不讓獲取,寫讀互斥 */ if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; //4,獲取讀鎖同步狀態 int r = sharedCount(c); /** *此處3個判斷條件 * 1.是否應該阻塞等待,這裏也是基於公平鎖和非公平獲取鎖實現 * 2.讀鎖同步狀態值是超過最大值,即限制獲取讀鎖的最大線程數 * 3.cas更新讀鎖同步狀態是否成功 */ if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { //能夠獲取到讀鎖 //r==0表示是第一個獲取讀鎖的線程 if (r == 0) { firstReader = current; //記錄第一個線程讀鎖的重入次數 firstReaderHoldCount = 1; } else if (firstReader == current) { //是第一個獲取讀鎖線程,鎖重入,鎖重入次數+1 firstReaderHoldCount++; } else { // 已有其餘線程獲取到讀鎖 /* *1,獲取緩存記錄的計數器,計數器是用來統計每個獲取讀鎖線程的重入次數的, *由每一個線程的ThreadLocal,即線程內的副本存儲,相互獨立; *此處也不是放入緩存,在有多個線程同時獲取讀鎖狀況, *用一個變量記錄上一個獲取讀鎖的線程的計數器,可能考慮屢次獲取讀鎖線程大機率是同一個線程狀況, *這樣作是可提升執行效率 */ HoldCounter rh = cachedHoldCounter; // rh==null,第一個獲取讀鎖,rh沒有值 // 或者計數器存儲的上一次線程的id與當前線程不等, 即不是相同一個線程, //那麼就獲取當前線程內部的計數器,並賦值給cachedHoldCounter變量,這樣可讓下一次獲取讀鎖線程獲取比較了 if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) /*進入該條件,我理解是在線程獲取讀鎖再釋放後,同一線程再次獲取讀鎖狀況, * 緩存計數器會記錄上一個線程計數器,由於線程釋放讀鎖後,count=0, * 這裏從新將計數器放入線程內部中, * 由於線程在使用完線程內部變量後會防止內存泄漏,會執行remove,釋放本地存儲的計數器。 */ readHolds.set(rh); //計數器+1 rh.count++; } return 1; } //上面3個條件沒有同時知足,沒有成功獲取到讀鎖,開始無限循環嘗試去獲取讀鎖 return fullTryAcquireShared(current); }
無限循環嘗試獲取共享鎖 fullTryAcquireShared方法
主要執行步驟:
一、 若是有其餘線程獲取到了寫鎖,寫讀互斥,應該去掛起等待;
二、若是能夠獲取讀鎖,判斷是否應該阻塞等待,在公平獲取鎖方式中,同步隊列中有其餘線程在等待,則應該去排隊按照FIFO順序獲取鎖,非公平獲取鎖方式,能夠直接去競爭獲取鎖。
三、能夠獲取鎖,則嘗試cas更新state的值,更新成功,獲取到鎖。
final int fullTryAcquireShared(Thread current){ HoldCounter rh = null; //無限循環 for (;;) { //獲取同步鎖狀態 int c = getState(); //判斷寫鎖值不爲0,且不是當前線程,不可獲取讀鎖 if (exclusiveCount(c) != 0) { if (getExclusiveOwnerThread() != current) return -1; } else if (readerShouldBlock()) { //沒有線程獲取到寫鎖狀況,公平獲取鎖狀況, //同步隊列中有其餘線程等待鎖,該方法主要是在須要排隊等待,計數器重入次數==0狀況,清除計數器 if (firstReader == current) { //此處firstReader !=null, 則第1個獲取讀鎖的線程還沒釋放鎖,可容許該線程繼續重入獲取鎖 //計數器count必定>0 } else { if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); if (rh.count == 0) //清除計數器 readHolds.remove(); } } // 爲何rh.count == 0就不讓線程獲取到鎖了,基於公平獲取鎖方式,去同步隊列中等待 if (rh.count == 0) return -1; } } //獲取讀鎖線程超過最大限制值 65535 if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); // cas執行讀鎖值+1 if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) { //1,第一個獲取讀鎖 firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { //2,第一個獲取讀鎖重入 firstReaderHoldCount++; } else { //3,非第一個線程獲取讀鎖,存在多個線程獲取讀鎖 if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; //緩存計數器變量記錄這次獲取讀鎖線程的計數器 cachedHoldCounter = rh; // cache for release } return 1; } } }
tryAcquireShared 返回< 0, 獲取鎖失敗,執行 doAcquireShared
在獲取讀鎖失敗後,執行如下步驟:
一、將節點加入同步隊列中
二、若是前置節點是頭節點,將再次嘗試獲取鎖,若是成功,設置當前節點爲head節點,並根據tryAcquireShared方法的返回值r判斷是否須要繼續喚醒後繼節點,若是 r大於0,須要繼續喚醒後繼節點,r=0不須要喚醒後繼節點。
三、若是前置節點不是頭節點,則在隊列中找到安全位置,設置前置節點 ws=SIGNAL, 掛起等待。
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); //r>=0,表示獲取到鎖, //r=0,表示不須要喚醒後繼節點 //r>0,須要繼續喚醒後繼節點 if (r >= 0) { //該方法實現2個步驟 //1,設置當前節點爲頭節點 //2,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); } }
setHeadAndPropagate 該方法是與獨佔鎖獲取鎖的區別之處,獲取到鎖後,設置爲頭結點還須要繼續傳播下去。
private void setHeadAndPropagate(Node node, int propagate) { //記錄是的舊的頭節點 Node h = head; // Record old head for check //設置當前獲取到鎖節點爲頭節點 setHead(node); //propagate >0,表示還須要繼續喚醒後繼節點 //舊的頭節點和新頭節點爲空,或者ws<0,知足條件之一,嘗試去喚醒後繼節點 if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; //後繼節點爲空或者是共享節點(獲取讀鎖的線程) if (s == null || s.isShared()) doReleaseShared(); } }
doReleaseShared 方法較難理解,在釋放鎖中也有調用,留着後面一塊兒分析。
public void unlock() { sync.releaseShared(1); }
AQS中釋放共鎖方法releaseShared
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
看一下讀寫鎖具體實現tryReleaseShared 的方法
protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); //1,更新或者移出線程內部計數器的值 if (firstReader == current) { //當前線程是第一個獲取讀鎖的線程 if (firstReaderHoldCount == 1) //直接置空 firstReader = null; else //該線程獲取讀鎖重入屢次,計數器-1 firstReaderHoldCount--; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); int count = rh.count; if (count <= 1) { //非第一個獲取讀鎖線程,避免ThreadLocal內存泄漏,移出計數器 readHolds.remove(); if (count <= 0) //此處是調用釋放鎖次數比獲取鎖次數還多狀況,直接拋異常 throw unmatchedUnlockException(); } --rh.count; } //2,循環cas更新同步鎖的值 for (;;) { int c = getState(); //讀鎖同步狀態-1 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. //返回徹底釋放讀鎖,讀鎖值是否==0,徹底釋放,等待寫鎖線程可獲取 return nextc == 0; } }
tryReleaseShared 返回true狀況,表示徹底釋放讀鎖,執行doReleaseShared,那就須要喚醒同步隊列中等待的其餘線程
在讀寫鎖中存在幾種狀況
狀況1、若是當前獲取鎖的線程佔用的是寫鎖,則後來不管是獲取讀鎖還寫鎖的線程都會被阻塞在同步隊列中,
同步隊列是FIFO隊列,在佔用寫鎖的釋放後,node1獲取讀鎖,因讀鎖是共享的,繼續喚醒後一個共享節點。
如上圖,在node1獲取到讀鎖時,會調用doReleaseShared方法,繼續喚醒下一個共享節點node2,能夠持續將喚醒動做傳遞下去,若是node2後面還存在幾個等待獲取讀鎖的線程,這些線程是由誰喚醒的?是其前置節點,仍是第一個獲取讀鎖的節點? 應該是第1個獲取鎖的節點,這裏即node1, 由下代碼可見,在無限循環中,只有頭節點沒有變化時,即再沒其餘節點獲取到鎖後,纔會跳出循環。
private void doReleaseShared() { for (;;) { //獲取同步隊列中頭節點 Node h = head; //同步隊列中節點不爲空,且節點數至少2個 if (h != null && h != tail) { int ws = h.waitStatus; //1,表示後繼節點須要被喚醒 if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases //喚醒後繼節點 unparkSuccessor(h); } //2,後繼節點暫時不須要喚醒,設置節點 ws = -3, 確保後面能夠繼續傳遞下去 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } //若是頭節點發生變化,表示已經有其餘線程獲取到鎖了,須要從新循環,確保能夠將喚醒動做傳遞下去。 if (h == head) // loop if head changed break; } }
一、在非公平獲取鎖方式下,是否存在等待獲取寫鎖的線程始終獲取不到鎖,每次都被後來獲取讀鎖的線程搶先,形成飢餓現象?
存在這種狀況,從獲取讀鎖源碼中看出,若是第一個線程獲取到讀鎖正在執行狀況下,第二個等待獲取寫鎖的線程在同步隊列中掛起等待,在第一個線程沒有釋放讀鎖狀況下,又陸續來了線程獲取讀鎖,由於讀鎖是共享的,線程均可以獲取到讀鎖,始終是在讀鎖沒有釋放完畢加入獲取讀鎖的線程,那麼等待獲取寫鎖的線程是始終拿不到寫鎖,致使飢餓。爲何默認仍是非公平模式?由於減小線程的上下文切換,保證更大的吞吐量。
一、讀寫鎖可支持公平和非公平兩種方式獲取鎖。
二、支持鎖降級,寫鎖可降級爲讀鎖,但讀鎖不可升級爲寫鎖。
三、大多數場景是讀多於寫的,因此ReentrantReadWriteLock 比 ReentrantLock(排他鎖)有更好的併發性能和吞吐量。
四、讀寫鎖中讀鎖和寫鎖都支持鎖重入。
五、在獲取Condition對象實現阻塞喚醒機制,ReentrantReadWriteLock.WriteLock 重寫了 newCondition方法,ReadLock不支持,即讀鎖不支持與Condition配合使用,使用阻塞喚醒機制。