Java併發包源碼學習系列:ReentrantReadWriteLock讀寫鎖解析

系列傳送門:

ReadWriteLock讀寫鎖概述

我們之前說到,ReentrantLock是獨佔鎖,某一時刻只有一個線程可以獲取該鎖,而實際上會存在很多讀多寫少的場景,而讀操作本身並不會存在數據競爭問題,如果使用獨佔鎖,可能會導致其中一個讀線程使其他的讀線程陷入等待,降低性能。

針對這種讀多寫少的場景,讀寫鎖應運而生。讀寫鎖允許同一時刻有多個讀線程訪問,但在寫線程訪問時,所有的讀線程和其他寫線程均被阻塞。我們先來看看Java中的讀寫鎖頂級接口吧,位於:java.util.concurrent.locks包下:

public interface ReadWriteLock {
    // 讀鎖
    Lock readLock();
	// 寫鎖
    Lock writeLock();
}

相信你會一下子就明白,讀寫鎖其實就是維護了一對鎖,一個寫鎖一個讀鎖,通過讀寫分離的策略,允許多個線程同時獲取讀鎖,大大提高併發性。

讀寫鎖案例

JavaDoc文檔寫的非常詳細,給我們舉了一個ReentrantReadWriteLock的使用例子,我們直接來看看:

class CachedData {
    Object data;
    volatile boolean cacheValid;
    // 創建讀寫鎖實例
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    void processCachedData() {
        // 獲取讀鎖
        rwl.readLock().lock();
        // 緩存失效的情況
        if (!cacheValid) { 
            
            // 釋放掉讀鎖,必須!在獲取寫鎖之前給讀鎖釋放了
            rwl.readLock().unlock();
            // 獲取寫鎖
            rwl.writeLock().lock();

            try {
                // 重新檢查狀態,因爲在等待寫鎖的過程中,可能前面有其他寫線程執行過了
                if (!cacheValid) { 
                    data = ...
                    cacheValid = true;
                }
                // 持有寫鎖的情況下,獲取讀鎖的,稱爲 “鎖降級”
                rwl.readLock().lock();
            } finally {
                // 釋放寫鎖,此時還剩一個讀鎖
                rwl.writeLock().unlock(); 
            }
        }

        try {
            use(data);
        } finally {
            // 釋放讀鎖
            rwl.readLock().unlock();
        }
    }
}

稍微總結一下,詳細的在後面的解析部分:

ReentrantReadWriteLock讀寫鎖分爲讀鎖和寫鎖,讀鎖是共享鎖,寫鎖是獨佔鎖。

持有寫鎖的線程可以繼續獲取讀鎖,稱爲鎖降級。

ReentrantReadWriteLock架構總覽

ReentrantReadWriteLock是ReadWriteLock的實現,其實看到這個名兒:可重入的讀寫鎖,我們就大概可以猜測一下它的意思了。除了實現了readLock()writeLock()兩個方法之外,還提供了一些重要方法,我們待會會一一解析。

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** 內部維護ReadLock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** 內部維護WriteL */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** 讀、寫鎖公用一個AQS的Sync的實例 */
    final Sync sync;
    
	/** 默認使用非公平模式 */
    public ReentrantReadWriteLock() {
        this(false);
    }
    /** 初始化讀鎖和寫鎖實例 */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

    /**
     * AQS的實現
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {   
        // ...
    }
    
     /**
     * Sync 非公平版本的實現
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }

    /**
     * Sync 公平版本的實現
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

    /**
     * 可以通過ReentrantReadWriteLock#readLock方法得到一個讀鎖實例
     */
    public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
        private final Sync sync;
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
        // 可以看到讀鎖使用的是共享模式
        public void lock() {
            sync.acquireShared(1);
        }
        public void unlock() {
            sync.releaseShared(1);
        }
        //...省略tryLock、lockInterruptibly等方法
    }

    /**
     * 可以通過ReentrantReadWriteLock#writeLock方法獲得一個寫鎖實例
     */
    public static class WriteLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -4992448646407690164L;
        private final Sync sync;
        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
        // 可以看到讀鎖使用的是獨佔模式
        public void lock() {
            sync.acquire(1);
        }
        public void unlock() {
            sync.release(1);
        }
        //...省略tryLock、lockInterruptibly等方法
    }

我們大概總結一下:

  • ReentrantReadWriteLock內部維護了ReadLock和WriteLock兩個內部類,他們都委託Sync實現具體功能【Sync是AQS的實現,這個之前講的非常清楚咯】。
  • 與ReentrantLock一樣,也提供了公平與非公平兩種實現:FairSync和NonfairSync,他們是Sync的實現類,兩種區別參見:公平與非公平策略的差異
  • ReadLock和WriteLock實例可以通過readLock()writeLock()兩個方法獲得。
  • ReadLock使用了共享模式、WriteLock使用了獨佔模式,兩者區別參見:Java併發包源碼學習系列:AQS共享式與獨佔式獲取與釋放資源的區別

Sync重要字段及內部類表示

我們在學習AQS的時候說到過,AQS的關鍵就是同步狀態字段state,例如以ReentrantLock爲例,它的state爲0表示鎖空閒,爲1表示有鎖被獲取,大於1表示鎖被同一個線程重入。

但已知讀寫鎖需要維護兩種狀態,僅用一個整型變量state如何表示呢?讀寫鎖利用按位切割的思想,巧妙地將state分割爲兩部分:

  • 高16位:表示讀狀態,代表讀鎖的獲取次數【包括重入次數】,由於共享模式,可以有多個線程獲取鎖,且可以重入。
  • 低16位:表示寫狀態,代表寫鎖的可重入次數,獨佔模式,只有一個線程可以獲得寫鎖,但是可以表示可重入次數。

注意區別這兩者的區別。

        /*
         * Read vs write count extraction constants and functions.
         * Lock state is logically divided into two unsigned shorts:
         * The lower one representing the exclusive (writer) lock hold count,
         * and the upper the shared (reader) hold count.
         */

        static final int SHARED_SHIFT   = 16;
		// 共享鎖狀態單位值 65536
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
		// 共享鎖線程最大個數 65535
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
		// 排他鎖掩碼 65535 二進制表示 15個1
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

        /** 返回讀鎖的獲取次數【包括重入次數】 無符號補0右移16位,其實就是獲取高16位 */
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** 返回寫鎖可重入次數 將高16位抹去,其實就是獲取低16位 */
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
  • sharedCount:無符號補0右移16位,其實就是獲取高16位。
  • exclusiveCount:將高16位抹去,其實就是獲取低16位。
		// 記錄每個線程持有的讀鎖數量
		static final class HoldCounter {
            // 持有的讀鎖數
            int count = 0;
            // Use id, not reference, to avoid garbage retention
            // 線程id
            final long tid = getThreadId(Thread.currentThread());
        }

        /**
         * ThreadLocal subclass. Easiest to explicitly define for sake
         * of deserialization mechanics.
         * ThreadLocal的子類
         */
        static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            // 每個線程都需要記錄獲取讀鎖的次數,判斷是否重入
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }

		// ThreadLocalHoldCounter繼承ThreadLocal
		// 存放除去第一個獲取讀鎖線程外的其他線程獲取讀鎖的可重入次數
        private transient ThreadLocalHoldCounter readHolds;
		// 記錄最後一個獲取讀鎖的線程獲取讀鎖的可重入次數
        private transient HoldCounter cachedHoldCounter;
		// 記錄第一個獲取到讀鎖的線程
        private transient Thread firstReader = null;
		// 記錄第一個獲取到讀鎖的線程獲取讀鎖的可重入次數
        private transient int firstReaderHoldCount;


        Sync() {
            // 初始化readHolds
            readHolds = new ThreadLocalHoldCounter();
            // 保證readHolds的內存可見性
            setState(getState()); // ensures visibility of readHolds
        }

寫鎖的獲取

ReentrantReadWriteLock中的寫鎖通過WriteLock實現。

void lock()

寫鎖是獨佔鎖,某一時刻只有一個線程可以獲取該鎖。

  • 如果當前沒有線程獲取到讀鎖寫鎖,則當前線程可以獲取到寫鎖然後返回。
  • 如果當前已經有線程獲取到到讀鎖和寫鎖,當前請求寫鎖的線程會被阻塞掛起。

寫鎖是可重入鎖,如果當前線程已經獲取該鎖,再次獲取只是簡單地把可重入次數+1後直接返回。

    // ReentrantReadWriteLock.WriteLock#lock
	public static class WriteLock implements Lock, java.io.Serializable {
        private final Sync sync;
        public void lock() {
            sync.acquire(1);
        }
    }

	// AQS # acquire
    public final void acquire(int arg) {
        // 調用子類實現的tryAcquire,如果位false,則加入阻塞隊列,阻塞
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

	// ReentrantReadWriteLock.Sync#tryAcquire
    abstract static class Sync extends AbstractQueuedSynchronizer {
        protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            // c != 0表示讀鎖或者寫鎖已經被某個線程獲取了
            if (c != 0) {
                // c != 0 && w == 0表示有線程獲取了讀鎖,share count此時不爲0。
                // c != 0 && w != 0並且當前線程不是寫鎖擁有者,返回false
                // 意思是隻要有讀鎖或寫鎖被佔用,這次獲取寫鎖就會失敗
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                
                //走到這裏說明當前線程就是已經獲取寫鎖的,判斷可重入的次數是否超過了最大值
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 設置可重入的次數,不需要CAS,因爲走到這裏必然是寫鎖重入
                setState(c + acquires);
                return true;
            }
            // 走到這,表示 c==0,此時爲第一個線程嘗試獲取寫鎖。
            // 如果寫鎖不需要block,進行cas操作,成功則表示獲取成功
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            // 經過前面的步驟之後,到這一步,才設置鎖的持有者爲當前線程
            setExclusiveOwnerThread(current);
            return true;
        }
    }

boolean writerShouldBlock()

writerShouldBlock方法實現,公平與非公平有差異:

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            // 返回是否存在前驅節點,會先看看前面有沒有在排隊的
            return hasQueuedPredecessors();
        }
    }

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        // 總是返回false,直接去cas
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
    }

很明顯了,對於非公平鎖來說,該方法永遠返回false,表示一定會且直接會走到compareAndSetState(c, c + acquires)這一步,通過CAS嘗試獲取寫鎖,獲取成功就設置狀態,之後當前線程會被設置爲鎖的持有者,失敗則返回false。

意思是:非公平模式下,會直接嘗試cas去搶這個寫鎖,搶不到再排隊;而對於公平模式來說,如果阻塞隊列中,當前線程存在前驅節點,就放棄CAS爭奪寫鎖的過程。

void lockInterruptibly()

類似於ReentrantLock的lockInterruptibly()方法,當其他線程調用了該線程的interrupt()方法中斷了當前線程時,當前線程就會拋出InterruptedException異常。

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
	//AQS
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

boolean tryLock()

嘗試獲取寫鎖,如果當前沒有其他線程持有寫鎖或讀鎖,則當前線程獲取寫鎖會成功,返回true。

如果當前已經有其他線程持有寫鎖或讀鎖則該方法直接返回false,且當前線程並不會被阻塞。

如果當前線程已經持有了該寫鎖則簡單增加AQS的狀態值後直接返回true。

public boolean tryLock( ) {
    return sync.tryWriteLock();
}

// AQS
final boolean tryWriteLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c != 0) {
        int w = exclusiveCount(c);
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    if (!compareAndSetState(c, c + 1))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

其實和lock方法的邏輯大差不大,只是採用lock方法的非公平鎖邏輯。

boolean tryLock(long timeout,TimeUnit unit)

類似於ReentrantLock的tryLock(long timeout,TimeUnit unit)方法。

嘗試獲取寫鎖,如果獲取失敗會將當前線程掛起指定時間,時間到了之後當前線程被激活,如果還是沒有獲取到鎖,就返回false。

另外,該方法會對中斷進行的響應,如果其他線程調用了當前線程的interrupt()方法,響應中斷,拋出異常。

寫鎖的釋放

void unlock()

嘗試釋放鎖,如果當前線程持有該鎖,調用該方法會讓該線程對該線程持有的AQS狀態減1,如果減1之後當前狀態值爲0,則當前線程會釋放該鎖。

如果當前線程沒有持有該鎖而調用了該方法,拋出IllegalMonitorStateException異常。

public void lock() {
    sync.acquireShared(1);
}
	// AQS
    public final boolean release(int arg) {
        // 嘗試釋放鎖
        if (tryRelease(arg)) {
            Node h = head;
            // 如果釋放成功,叫醒後繼節點
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
	// ReentrantReadWriteLock.Sync#tryAcquire
    abstract static class Sync extends AbstractQueuedSynchronizer {
        protected final boolean tryRelease(int releases) {
            // 當前線程沒有持有該鎖而調用了該方法
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            // 判斷一下是不是需要釋放鎖了
            boolean free = exclusiveCount(nextc) == 0;
            // 清空一下
            if (free)
                setExclusiveOwnerThread(null);
            // state沒有到0,僅僅是設置state而已
            setState(nextc);
            // 如果寫鎖全部釋放,返回true,上面的方法就會喚醒之後的節點
            return free;
        }
    }

讀鎖的獲取

ReentrantReadWriteLock中的讀鎖通過ReadLock實現,ps:讀鎖的獲取與釋放相對於寫鎖來說,較爲複雜。

void lock()

    // ReentrantReadWriteLock.ReadLock#lock
	public static class ReadLock implements Lock, java.io.Serializable {
        private final Sync sync;
        public void lock() {
            sync.acquireShared(1);
        }
    }

	// AQS # acquireShared
    public final void acquireShared(int arg) {
        // 調用子類實現的tryAcquireShared,如果爲false,則加入阻塞隊列,阻塞
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

	// ReentrantReadWriteLock.Sync#tryAcquire
    abstract static class Sync extends AbstractQueuedSynchronizer {
        protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            // 獲取當前狀態值
            int c = getState();
            
            if (exclusiveCount(c) != 0 && // 說明有線程持有寫鎖
                getExclusiveOwnerThread() != current) // 並且不是當前線程持有寫鎖
                return -1; // 失敗
            
            //----- 這裏提一嘴,上面如果持有寫鎖的是自己,就還是可以獲取讀鎖的 -----//
            
            // 獲取讀鎖計數
            int r = sharedCount(c);
            
            // 讀鎖獲取是否需要阻塞,若不成功將會進入fullTryAcquireShared進行重試
            if (!readerShouldBlock() &&
                r < MAX_COUNT && // 判斷讀鎖獲取次數是否溢出
                // 嘗試將高16位+1,低16位不變,如果獲取成功則表示獲取到了讀鎖
                compareAndSetState(c, c + SHARED_UNIT)) { 
                
                // ----- 能走到這裏表示當前線程獲取讀鎖成功 ----- //
                
                // r==0表示第一個線程獲取讀鎖 ,也有可能之前有線程但被釋放了,當前自然就是第一個啦
                if (r == 0) {
                    firstReader = current; // 記錄一下firstReader【每次將讀鎖獲取次數從0變成1】
                    firstReaderHoldCount = 1; // 記錄一下持有的讀鎖數量 1
                    
                // 來到這裏 c != 0 且 firstReader == current ,表示firstReader可重入了
                } else if (firstReader == current) {
                    firstReaderHoldCount++; // 直接計數加1
                } else {
              		// cachedHoldCounter 使用來緩存最後一個獲取讀鎖的線程的,之後用rh表示
                    HoldCounter rh = cachedHoldCounter;
                    
                    // 如果rh還沒緩存 或者 存的不是當前線程
                    if (rh == null || rh.tid != getThreadId(current))
                        // 那就更新一下cachedHoldCounter 爲當前線程的HoldCounter
                        cachedHoldCounter = rh = readHolds.get();
                    // 走到這裏,說明緩存的是當前線程,但是count是0
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    // count 加1
                    rh.count++;
                }
                return 1; // 大於0表示獲取到了共享鎖
            }
            // 類似tryAcquireShared,自旋獲取,這裏失敗的話,就得進阻塞隊列去了嗷
            return fullTryAcquireShared(current);
        }
    }

boolean readerShouldBlock()

readerShouldBlock方法實現,公平與非公平有差異:

    static final class FairSync extends Sync {

        final boolean readerShouldBlock() {
            // 看看阻塞隊列中是否已經有其他元素在排隊
            return hasQueuedPredecessors();
        }
    }

    static final class NonfairSync extends Sync {

        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            // 判斷阻塞隊列中 第一個節點是否是來獲取寫鎖的,如果是的話,讓這個寫鎖先來。
            return apparentlyFirstQueuedIsExclusive();
        }
    }

具體看下非公平鎖的實現,apparentlyFirstQueuedIsExclusive方法:

    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null && // 隊列是否爲空
            (s = h.next)  != null && // 是否存在第一個元素
            !s.isShared()         && // 第一個元素是否正在嘗試獲取寫鎖
            s.thread != null;		 // 該元素的線程是否爲null
    }

聯繫起來解釋:

  1. !readerShouldBlock():如果隊列裏面存在一個元素,判斷第一個元素是不是正在嘗試獲取寫鎖,如果是的話,這個讓這個元素先來,它的優先級很高。
  2. r < MAX_COUNT:判斷當前獲取讀鎖的線程是否達到最大值。
  3. compareAndSetState(c, c + SHARED_UNIT):執行CAS操作將AQS狀態值的高16位值增加1

其實就是:看看隊列裏面的第一個節點是不是在嘗試獲取寫鎖,如果是的話,就讓他先來。如果你在獲取讀鎖,那不好意思,乖乖地去CAS吧,看誰能搶到。

如果沒有獲取到讀鎖,會怎麼辦呢?進入fullTryAcquireShared邏輯看看:

int fullTryAcquireShared(Thread)

        /**
         * Full version of acquire for reads, that handles CAS misses
         * and reentrant reads not dealt with in tryAcquireShared.
         */
        final int fullTryAcquireShared(Thread current) {
            /*
             * 這段代碼和tryAcquireShared部分冗餘,但總體更加簡單
             */
            HoldCounter rh = null;
            // 自旋
            for (;;) {
                int c = getState();
                // 已經有線程獲取了寫鎖
                if (exclusiveCount(c) != 0) {
                    // 且獲取寫鎖的線程不是當前線程,那就直接進隊,如果是當前線程,走到cas去,鎖降級的過程
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                
                } else if (readerShouldBlock()) {
                    // 走到這一步,表示寫鎖沒被佔有,且阻塞隊列中有其他線程在等待
                    // firstReader線程重入讀鎖,直接快進到下面的cas
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            // cachedHoldCounter 沒有緩存或緩存的不是當前線程
                            if (rh == null || rh.tid != getThreadId(current)) {
                                // 如果當前線程從來沒有初始化ThreadLocal中的值,get方法會進行初始化
                                rh = readHolds.get();
                                // 表示上一行代碼是初始化的,執行remove
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        // 排隊
                        if (rh.count == 0)
                            return -1;
                    }
                }
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // cas操作成功,表示獲取讀鎖了,接下來設置firstReader或firstReaderHoldCount
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        // 將cachedHoldCounter設置爲當前線程
                        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;
                }
            }
        }

接下來讀鎖的幾個方法和寫鎖其實差不太多,源碼就不貼了,感興趣的小夥伴可以自己看看。

void lockInterruptibly()

類似於lock()方法,區別在於,該方法能夠中斷響應,當其他線程調用該線程的interrupt()方法中斷了當前線程時,當前線程拋出InterruptedException異常。

boolean tryLock()

嘗試讀取鎖,如果當前沒有其他線程持有寫鎖,則當前線程會獲取讀鎖成功,返回true。

如果當前已經有其他線程持有寫鎖,則直接返回false,不會阻塞。

如果當前線程已經持有了該讀鎖,則利用AQS將state的高16位加1,返回true。

boolean tryLock(long timeout,TimeUnit unit)

類似於tryLock,不同的是,設定了超時時間,超時時間到了,如果沒有讀取到讀鎖,直接返回false。

可中斷響應,當其他線程調用該線程的interrupt()方法中斷了當前線程時,當前線程拋出InterruptedException異常。

讀鎖的釋放

void unlock()

public void unlock() {
    sync.releaseShared(1);
}
// AQS
public final boolean releaseShared(int arg) {
    // 如果tryReleaseShared返回true,釋放一個由於獲取寫鎖而被阻塞的線程
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
abstract static class Sync extends AbstractQueuedSynchronizer {
    protected final boolean tryReleaseShared(int unused) {
        Thread current = Thread.currentThread();
        if (firstReader == current) {
            // 如果firstReaderHoldCount爲1,這次解鎖之後,就會變成0了,將firstReader設置爲null
            if (firstReaderHoldCount == 1)
                firstReader = null;
            else
                // 否則減1就可以
                firstReaderHoldCount--;
        } else {
            // 判斷cacheHoldCounter是否緩存的是當前線程,如果不是的話,需要從ThreadLocal中取。
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                rh = readHolds.get();
            int count = rh.count;
            if (count <= 1) {
                // 不再持有鎖了,調用remove,防止內存泄露
                readHolds.remove();
                if (count <= 0)
                    throw unmatchedUnlockException();
            }
            --rh.count;
        }
        // 無限循環,保證CAS操作成功
        for (;;) {
            // 獲取狀態值
            int c = getState();
            int nextc = c - SHARED_UNIT;
            // CAS  操作更新狀態值。CAS操作如果不成功,會一直循環
            if (compareAndSetState(c, nextc))
                // 如果更新成功,查看當前狀態值是否爲0,如果爲0說明已經沒有讀線程佔用讀鎖
                // 如果不爲0,則說明還有其他線程持有讀鎖,返回false
                return nextc == 0;
        }
    }
}



鎖降級的理解

鎖降級就意味着寫鎖是可以降級爲讀鎖的,但是需要遵循先獲取寫鎖、獲取讀鎖在釋放寫鎖的次序。注意如果當前線程先獲取寫鎖,然後釋放寫鎖,再獲取讀鎖這個過程不能稱之爲鎖降級,鎖降級一定要遵循那個次序。

注意,作者Doug Lea並沒有說寫鎖更爲高級,如果有線程持有讀鎖,那麼寫鎖獲取也需要等待,但源碼中確實可以看出給寫鎖一些特殊照顧,如在非公平模式下,爲了提高吞吐量,如果發現第一個節點是獲取寫鎖的線程,直接獲取成功。

鎖降級的部分,源碼中是這樣體現的:

int c = getState();
// 已經有線程獲取了寫鎖
if (exclusiveCount(c) != 0) {
    // 且獲取寫鎖的線程不是當前線程,那就直接進隊,如果是當前線程,走到cas去,鎖降級的過程
    if (getExclusiveOwnerThread() != current)
        return -1;

鎖降級中讀鎖的獲取是否必要?

假如當前線程 A 不獲取讀鎖而是直接釋放了寫鎖,這個時候另外一個線程 B 獲取了寫鎖,那麼這個線程 B 對數據的修改是不會對當前線程 A 可見的。

如果獲取了讀鎖,則線程B在獲取寫鎖過程中判斷如果有讀鎖還沒有釋放則會被阻塞,只有當前線程 A 釋放讀鎖後,線程 B 纔會獲取寫鎖成功。

總結

  • ReentrantReadWriteLock底層使用AQS實現,利用AQS的狀態值的高16位表示獲取到讀鎖的個數,低16位標識獲取到寫鎖的線程的可重入次數,通過CAS對其進行操作實現讀寫分離,適用於讀多寫少的場景。

  • ReentrantReadWriteLock的三個特性:

    • 公平性:支持公平和非公平兩種模式。
    • 重入性:支持重入,讀寫鎖都支持最多65535個。
    • 鎖降級:先獲取寫鎖,再獲取讀鎖,再釋放寫鎖,寫鎖就能降級爲讀鎖。
  • 讀寫鎖:讀寫鎖允許同一時刻有多個讀線程訪問,但在寫線程訪問時,所有的讀線程和其他寫線程均被阻塞。

參考閱讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章