Java鎖lock源碼分析(三)讀寫鎖

Java鎖lock源碼分析(三)讀寫鎖

摘自網上一段話:
ReadWriteLock管理一組鎖,一個是隻讀的鎖,一個是寫鎖。讀鎖可以在沒有寫鎖的時候被多個線程同時持有,寫鎖是獨佔的。
所有讀寫鎖的實現必須確保寫操作對讀操作的內存影響。換句話說,一個獲得了讀鎖的線程必須能看到前一個釋放的寫鎖所更新的內容。
讀寫鎖比互斥鎖允許對於共享數據更大程度的併發。每次只能有一個寫線程,但是同時可以有多個線程併發地讀數據。ReadWriteLock適用於讀多寫少的併發情況。

前文Java鎖Lock源碼分析(一)提過在java的Lock中獲取鎖就表示AQS的volatile int state =1表示獲取到了獨佔鎖,state>1表示當前線程重入鎖(獲取鎖了再次獲取到了鎖)即大於0就表示獲取到了獨佔鎖。

獨佔就意味着排隊,失敗,系統吞吐量下降,用戶體驗下降等等。有些情況不要獨佔,比如說讀與讀不互斥,讀與寫互斥(但是寫鎖可降級爲讀鎖),寫與寫互斥的這樣會提升系統的吞吐量。讀寫鎖就是完成這個功能的主角,先上一段代碼坐下證明:

讀寫請求模擬:
讀寫鎖測試代碼1

寫鎖代碼:
寫鎖

讀鎖代碼:
讀鎖

我們看下最關鍵結果:
測試結果

這個結果已經說明問題了,讀取的時候沒有寫入,寫入的時候沒有其他寫入,沒有其它讀,讀的時候有其它讀,很直白。

那麼問題就來了,之前說過,AQS的成員volatile int state就是表示獲取成功了鎖,那麼讀寫鎖是怎麼做到讀與讀不互斥而共享的,寫與寫是怎麼互斥的。

1)AQS的volatile int state;int類型是32位的高16位表示寫鎖,低16位表示讀鎖。一個字段技能表示讀鎖,也能表示寫鎖。
2)實現原理:寫鎖即低16位c & (1<<16 -1) 讀鎖即高16位 c >>> 16得出的值表示AQS的state

ReentrantReadWriteLock的抽象AQS實現Sync
源碼中有幾行註釋方法和屬性就是上面兩句話的總結:
讀寫鎖實現

上面的代碼已經很清楚的演示了ReentrantReadWriteLock的使用方法。
讀鎖:
我們先講寫鎖的獲取(相比於讀鎖它看起來更加簡單)
ReentrantReadWriteLock.WriteLock.lock()

public void lock() {
    sync.acquire(1);
}
//同樣的還是進入到了AQS的模板方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
}

主要還是看下ReentrantReadWriteLock是如何重寫tryAcquire(1)方法的:

protected final boolean tryAcquire(int acquires) {
     Thread current = Thread.currentThread();
     int c = getState();
     //1)w=c&(1<< 16-1)寫鎖的數量 大於0一定是同一個線程重入的
     int w = exclusiveCount(c);
     //2)有線程佔用過鎖
     if (c != 0) {
         //3)被寫鎖獨佔,不是當前線程(其他線程獲取到的寫鎖)則失敗
         if (w == 0 || current != getExclusiveOwnerThread())
             return false;
         //4)被寫鎖獨佔,而且是當前線程重入的次數大於1<<16-1則拋出異常    
         if (w + exclusiveCount(acquires) > MAX_COUNT)
             throw new Error("Maximum lock count exceeded");
         //5)被寫鎖獨佔,是當前線程獲取到的鎖,重入
         setState(c + acquires);
         return true;
     }
     //6)沒有線程持有鎖
     if (writerShouldBlock() ||
         !compareAndSetState(c, c + acquires))
         return false;
     setExclusiveOwnerThread(current);
     return true;
 }

上面的註釋已經是非常清楚,這裏只是對第6)做下補充:
既然一下線程也沒有獲取到鎖爲什麼不直接cas設置搶佔鎖呢?不要忘記ReentrantReadWriteLock也是支持公平鎖和費公平鎖的,也支持重入鎖

公平鎖)FailSync判斷AQS的雙向鏈表有沒有節點,有的話直接返回失敗,沒有的話CAS搶佔一下 –公平的體現 沒有排隊才搶佔一下,所以writeShouldBlock是判斷隊列中有無排隊的。
非公平鎖)NonfairSync直接不管有沒有排隊的直接搶一下 所以writeShouldBlock什麼也不判斷直接返回false

PS:6)中失敗即公平鎖隊列中有排隊的或者 cas在失敗,改線程是如何進入到隊列(雙向鏈表)的可以參考之前的兩篇文章Java鎖lock源碼分析(一),這裏不做解釋了。

ReentrantReadWriteLock.ReadLock.lock()

public void lock() {
     sync.acquireShared(1);
}

還是走的AQS的模板方法:
共享鎖實現類
PS:JUC包下的幾個重要線程同步工具類如Semaphore、CountDownLatch、ReentrantReadWriteLock正式共享鎖的實現,它們都是重寫了AQS的tryAcquireShared(args)方法。

我們看下在ReentrantReadWriteLock中的tryAcquireShared方法的具體實現:

protected final int tryAcquireShared(int unused) {
     Thread current = Thread.currentThread();
     int c = getState();
     //1)判斷寫鎖 c & (1<<16-1)!=0,寫鎖不是當前線程
     if (exclusiveCount(c) != 0 &&
         getExclusiveOwnerThread() != current)
         return -1;
     //2)c >>>16
     int r = sharedCount(c);
     //3)等待隊列沒有寫線程,成功獲取到讀鎖的線程數少於1<<16-1,cas設置成功
     if (!readerShouldBlock() &&
         r < MAX_COUNT &&
         compareAndSetState(c, c + SHARED_UNIT)) {
         if (r == 0) {
             firstReader = current;
             firstReaderHoldCount = 1;
         } 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++;
         }
         return 1;
     }
     //cas不成功,超出讀鎖數量,或則寫線程在排隊  多次重試
     return fullTryAcquireShared(current);
 } 

補充下:

對於1)判斷有無寫鎖跟讀鎖互斥這個這正常,後邊還有一個判斷獨佔寫鎖是否是當前線程—寫鎖是可以降級爲讀鎖的
對於3)那讀鎖來說AQS的state通過計算sharedCount(state)表示的讀鎖的總數,但是每個線程能獲取多少個鎖是沒有統計,後期的釋放鎖就會有問題所以多出來了HoldCounter來表示當前線程獲取鎖的個數(能重入的)

讀寫線程是如何進入到等待隊列,公平鎖與非公平鎖的區別,如何重入以前都說過Java鎖lock源碼分析(一),我們只需要知道讀寫鎖還是操作的AQS的state變量,高位表示寫鎖,低位表示讀鎖,

ReentrantReadWriteLock的抽象AQS實現Sync
獲取鎖的總數量:

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();
    }
}
private transient ThreadLocalHoldCounter readHolds;
private transient HoldCounter cachedHoldCounter;

private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
Sync() {
    readHolds = new ThreadLocalHoldCounter();
    setState(getState()); // ensures visibility of readHolds
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章