ReentrantReadWriteLock原始碼分析及理解

bmilk發表於2020-06-04

本文結構

  • 讀寫鎖簡介:介紹讀寫鎖、讀寫鎖的特性以及類定義資訊
  • 公平策略及Sync同步器:介紹讀寫鎖提供的公平策略以及同步器原始碼分析
  • 讀鎖:介紹讀鎖的一些常用操作和讀鎖的加鎖、解鎖的原始碼分析
  • 寫鎖:介紹寫鎖的一些常用操作和寫鎖的加鎖、解鎖的原始碼分析
  • 總結:總結全文,附讀寫鎖全部原始碼理解

讀寫鎖簡介

在之前的文章提到了可重入鎖,這是一種排他鎖,核心原理是同一時間只允許一個執行緒訪問。除了排他鎖還有一種共享鎖,這種鎖在同一時間支援多執行緒同時訪問,
將排他鎖和共享鎖進行組合便有了讀寫鎖。讀寫鎖維護了一組鎖——讀鎖和寫鎖。讀鎖在同一時間可以有多個執行緒共同訪問,是一個共享鎖;而寫鎖在同一時間僅支援
一個執行緒訪問,是一個排他鎖。通過讀鎖的允許多個執行緒同時訪問,使得併發性相比單純的排他鎖效率有很大的提升。

在讀寫鎖中,需要保證寫鎖對於讀鎖的可見性,也就是說當寫鎖更改資料之後讀鎖需要能夠立刻獲知。假設有一組執行緒訪問同一個快取區,其中只有一個執行緒向其中寫資料,
其他的執行緒都是讀資料,這塊區域大部分的時間都是使用者的讀操作,只有很少的時間是寫操作,多個執行緒的讀並不會相互影響,那麼就可以使用讀寫鎖,只需要保證寫操作
之後,資料立刻對於其他的讀操作可見即可。

\(\color{#FF3030}{讀寫鎖是一個鎖,只是可以進行共享或者排他的兩種操作模式。}\)讀寫鎖是一個鎖,只是可以進行共享或者排他的兩種操作模式。

讀寫鎖的特性

一般情況下,讀寫鎖的效能會優於排他鎖,因為程式中大多數場景都是讀取資料,很少一部分是寫資料。在讀取併發多的情況下,可以提供比排他鎖更好的吞吐量。

特性 說明
公平性 讀寫鎖可以選擇公平和非公平性兩種特性,預設為非公平模式,並且吞吐量非公平由於公平模式
可重入性 讀(寫)鎖支援執行緒的重入。當一個執行緒獲取讀(寫)鎖後,這個執行緒可以再次獲取這個讀(寫)鎖
鎖降級 當一個執行緒獲取寫鎖之後,可以獲取讀鎖,在釋放寫鎖完成鎖降級過程

讀寫鎖的定義

ReentrantReadWriteLock簡單分析,主要介紹類由哪些部分組成及每部分的作用,具體的實現後面按照內部類及提供主要操作細解

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** Inner class providing readlock */
    //讀鎖,讀鎖類是讀寫鎖的內部類
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    //寫鎖,寫鎖類是讀寫鎖的內部類
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics */
    //同步器,完成核心的加鎖釋放鎖的過程,公平機制委派其子類實現
    final Sync sync;

    /**
     * Creates a new {@code ReentrantReadWriteLock} with
     * default (nonfair) ordering properties.
     */
    //預設的讀寫鎖建構函式,預設使用非公平模式
    public ReentrantReadWriteLock() {
        this(false);
    }

    /**
     * Creates a new {@code ReentrantReadWriteLock} with
     * the given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    //帶公平策略選擇的構造器,其中引數為true代表公平模式,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; }
    
    //同步器類,實現核心的加鎖解鎖同步佇列的操作,委派子類實現公平非公平策略
    abstract static class Sync extends AbstractQueuedSynchronizer {......}

    //非公平模式同步器,繼承Sync並實現非公平策略
    static final class NonfairSync extends Sync{....}

    //公平模式同步器,繼承Sync並實現公平策略
    static final class FairSync extends Sync{....}

    //寫鎖類,使用與外部類一致的公平策略
    public static class WriteLock implements Lock, java.io.Serializable{......}

    //讀鎖類,使用與外部類一致的公平策略
    public static class ReadLock implements Lock, java.io.Serializable{......}

公平策略及Sync同步器

讀寫鎖提供了公平與非公平策略,並由Sync的子類實現。NonfairSyncFairSync主要用來判斷獲取讀鎖和寫鎖的時候是否需要阻塞,
其獲取鎖的過程全部交由Sync實現。也可以說使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() {
        /* 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();
    }
}

//這個方法在AQS中實現,目的就是執行的操作就是判斷佇列的頭節點的後繼節點是不是獲取寫鎖
final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    return (h = head) != null &&
        (s = h.next)  != null &&
        !s.isShared()         &&
        s.thread != null;
}

公平策略

公平版本的同步器,詳細解釋見原始碼註釋

//相比非公平版本就會簡單很多,只需要判斷佇列中是否有現成在等待就可以
static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

Sync同步器

Sync實現了主要的加鎖解鎖的操作。讀寫鎖同樣依賴AQS來實現同步功能,在AQS中由State整型欄位代表所數量而這裡是兩種鎖,因此使用位操作來代表不同的鎖,
使用高16位代表共享鎖(讀鎖),低16位代表排他鎖(寫鎖)如下圖,並且所有與為操有關的事情都在這裡完成,AQS中僅提供一些判斷介面及佇列操作。

具體程式碼解釋見註釋

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 6317671515068378041L;

    /*
     * 讀鎖和寫鎖的一些常數和函式
     * 將鎖狀態在邏輯上分為兩個無符號的短整型
     * 低位代表排他鎖(寫鎖)的計數器
     * 高位代表共享鎖(讀鎖)的計數器
     */

    //共享鎖的偏移量——16位
    static final int SHARED_SHIFT   = 16;
    //共享鎖計數器加一或者減一的基數
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    //鎖計數器的最大值,做最大可以被同時或者重入獲取的次數
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    //寫鎖的掩碼 和鎖狀態state按位與操作可以得到寫鎖的計數器
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

    //計算共享鎖的持有量(讀鎖)
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    //計算排他鎖的持有量(寫鎖)
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

    /**
     * 每個執行緒持有的讀鎖的計數器.
     * 作為ThreadLocal維護; 最後一成功加鎖的執行緒資訊快取在cachedHoldCounter
     */
    static final class HoldCounter {
        //執行緒持有的讀鎖的數量
        int count = 0;
        // Use id, not reference, to avoid garbage retention
        //使用id而不使用引用避免垃圾保留
        final long tid = getThreadId(Thread.currentThread());
    }

    /**
     * ThreadLocal 子類. 重寫了initialValue方法,
     * 在第一次get時(之前也沒進行過set操作)返回count值為0而不是null
     */
    static final class ThreadLocalHoldCounter
        extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }

    /**
     * The number of reentrant read locks held by current thread.
     * Initialized only in constructor and readObject.
     * Removed whenever a thread's read hold count drops to 0.
     */
    //讀鎖被當前執行緒持有的次數,僅在建構函式和readObject中初始化
    //當執行緒的讀鎖持有數量為0時刪除
    private transient ThreadLocalHoldCounter readHolds;

    /**
     * The hold count of the last thread to successfully acquire
     * readLock. This saves ThreadLocal lookup in the common case
     * where the next thread to release is the last one to
     * acquire. This is non-volatile since it is just used
     * as a heuristic, and would be great for threads to cache.
     *
     * <p>Can outlive the Thread for which it is caching the read
     * hold count, but avoids garbage retention by not retaining a
     * reference to the Thread.
     *
     * <p>Accessed via a benign data race; relies on the memory
     * model's final field and out-of-thin-air guarantees.
     */
    //最後一個成功獲取讀鎖的執行緒持有的讀鎖的數量
    private transient HoldCounter cachedHoldCounter;

    /**
     * firstReader is the first thread to have acquired the read lock.
     * firstReaderHoldCount is firstReader's hold count.
     *
     * <p>More precisely, firstReader is the unique thread that last
     * changed the shared count from 0 to 1, and has not released the
     * read lock since then; null if there is no such thread.
     *
     * <p>Cannot cause garbage retention unless the thread terminated
     * without relinquishing its read locks, since tryReleaseShared
     * sets it to null.
     *
     * <p>Accessed via a benign data race; relies on the memory
     * model's out-of-thin-air guarantees for references.
     *
     * <p>This allows tracking of read holds for uncontended read
     * locks to be very cheap.
     */
    //最後一個將讀鎖計數器從0改為1的執行緒,並且一直沒有釋放讀鎖
    //如果不存在這個執行緒則為null
    private transient Thread firstReader = null;
    private transient int firstReaderHoldCount;

    //建構函式
    Sync() {
        readHolds = new ThreadLocalHoldCounter();
        setState(getState()); // ensures visibility of readHolds
    }

    /*
     * Acquires and releases use the same code for fair and
     * nonfair locks, but differ in whether/how they allow barging
     * when queues are non-empty.
     */

    /**
     * Returns true if the current thread, when trying to acquire
     * the read lock, and otherwise eligible to do so, should block
     * because of policy for overtaking other waiting threads.
     */
    //獲取和釋放讀寫鎖,公平版本和非公平版本使用同樣的程式碼結構
    //但在當前執行緒是否需要排隊阻塞,如何阻塞方面存在差異
    //返回true表示當前執行緒試圖獲取讀鎖應當被阻塞,
    abstract boolean readerShouldBlock();

    /**
     * Returns true if the current thread, when trying to acquire
     * the write lock, and otherwise eligible to do so, should block
     * because of policy for overtaking other waiting threads.
     */
    //true表示當前執行緒嘗試獲取寫鎖應該被阻塞
    abstract boolean writerShouldBlock();

    /*
     * Note that tryRelease and tryAcquire can be called by
     * Conditions. So it is possible that their arguments contain
     * both read and write holds that are all released during a
     * condition wait and re-established in tryAcquire.
     */
    
    //releases:釋放寫鎖的次數,該值小於等於當前執行緒持有的寫鎖的數量
    //返回這個執行緒是否繼續持有這個寫鎖
    protected final boolean tryRelease(int releases) {
        
        //判斷當前執行緒是否持有這個排他鎖
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        //計算釋放之後持有這個鎖的次數
        int nextc = getState() - releases;
        boolean free = exclusiveCount(nextc) == 0;
        //釋放後不在持有這個寫鎖
        if (free)
            //設定鎖屬於的執行緒為null
            setExclusiveOwnerThread(null);
        //設定鎖的狀態
        setState(nextc);
        return free;
    }

    //嘗試獲取寫鎖
    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.
         */
        /*
         * 需要完成的工作:
         * 1. 如果鎖狀態(包含讀鎖和寫鎖)不為0,並且當前執行緒沒有持有寫鎖則失敗
         * 2. 如果寫鎖計數器大於最大值則獲取失敗
         * 3. 否則如果是重入的獲取鎖,則會被允許.
         */
        Thread current = Thread.currentThread();
        //獲取鎖狀態
        int c = getState();
        //獲取寫鎖的鎖狀態
        int w = exclusiveCount(c);
        //如果鎖被不為null即可能被任何一個執行緒持有
        if (c != 0) {
            // (Note: if c != 0 and w == 0 then shared count != 0)
            //w==0則讀鎖被某個執行緒持有或者寫鎖被其他執行緒持有則獲取鎖失敗
            //進入佇列排隊
            if (w == 0 || current != getExclusiveOwnerThread())
                return false;

            //寫所已經被當前執行緒持有則判斷再次加鎖後是否會超過寫鎖的最大可以被加鎖的次數
            //超過則加鎖失敗
            if (w + exclusiveCount(acquires) > MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            // Reentrant acquire
            //重入的獲取鎖
            setState(c + acquires);
            return true;
        }
        //鎖沒有被任何執行緒持有,則需要根據公平策略來判斷當前執行緒是否需要阻塞
        //公平鎖:檢查同步等待佇列,若佇列中存在等待時間更長的執行緒則需要阻塞
        //非公平鎖:可以搶佔式獲取寫鎖,不需要阻塞

        //需要被阻塞或者CAs操作失敗則進入同步佇列
        if (writerShouldBlock() ||
            !compareAndSetState(c, c + acquires))
            return false;
        //不需要阻塞並且加鎖成功,設定排他鎖的所屬執行緒資訊
        setExclusiveOwnerThread(current);
        return true;
    }

    //嘗試釋放共享鎖,與加共享鎖一致,只能一個一個的釋放
    //unused引數沒有被使用
    protected final boolean tryReleaseShared(int unused) {
        Thread current = Thread.currentThread();
        //當前執行緒是firstReader並且僅持有一次讀鎖,在釋放讀鎖後firstReader應該置null
        if (firstReader == current) {
            // assert firstReaderHoldCount > 0;
            if (firstReaderHoldCount == 1)
                firstReader = null;
            else
                firstReaderHoldCount--;
        } else {
            //獲取當前執行緒的HoldCounter資訊
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                rh = readHolds.get();
            int count = rh.count;
            //當讀誦持有數量歸零時,會從執行緒的threadLocals中刪除readHolds
            if (count <= 1) {
                readHolds.remove();
                //沒持有鎖的縣城不能釋放鎖
                if (count <= 0)
                    throw unmatchedUnlockException();
            }
            --rh.count;
        }
        //使用cas操作原子的減少鎖狀態,避免CAS操作失敗的情況
        for (;;) {
            int c = getState();
            //減少一個讀鎖
            int nextc = c - SHARED_UNIT;
            if (compareAndSetState(c, nextc))
                // 釋放讀鎖對於讀操作沒有影響,
                // 但是如果現在讀鎖和寫鎖都是空閒的
                // 可能會使等待的獲取寫鎖的操作繼續
                
                //返回鎖是否還被任何一個執行緒持有
                return nextc == 0;
        }
    }

    private IllegalMonitorStateException unmatchedUnlockException() {
        return new IllegalMonitorStateException(
            "attempt to unlock read lock, not locked by current thread");
    }

    //獲取讀鎖
    //unused引數沒有被使用,一個一個加鎖
    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.
         */
        /*
         * 待辦事項:
         * 1. 如果寫鎖被其他執行緒獲取,則失敗.
         * 2. 否則這個執行緒就有資格使用鎖的狀態,因此需要判斷是否因為
         *    因為同步等待策略而阻塞,否則嘗通過cas操作嘗試授予鎖
         *    可重入性的操作在fullTryAcquireShared中進行
         *    避免在不可重入的情況下檢查鎖狀態
         * 3. 如果步驟2失敗,因為執行緒不符合條件或者cas失敗
         *    則進入fullTryAcquireShared中迴圈重試
         */
        Thread current = Thread.currentThread();
        int c = getState();
            //寫鎖被其他執行緒持有則獲取讀鎖失敗,需要進入同步等待佇列
        if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
            return -1;
        int r = sharedCount(c);
            
            /**
             * 判斷當前執行緒是否需要阻塞
             * 不同的公平策略有不同的判斷方式
             * 非公平模式下,如果存在同步等待佇列且第一個是嘗試獲取寫鎖的
             * 其他執行緒則需要阻塞
             * 公平模式下,佇列中存在排隊等待的執行緒則需要進入佇列等待
             */
    
            //如果當前執行緒已經獲取樂寫鎖,則這可以是一個鎖降級的過程
            //不用進入佇列排隊
        if (!readerShouldBlock() &&
            //鎖的獲取次數不能超過最大的可獲取的次數
            r < MAX_COUNT &&
            //不需要阻塞,鎖的計數沒有超過最大值則嘗試通過cas操作加鎖
            //可能會失敗,如果存在多個執行緒同時競爭
            compareAndSetState(c, c + SHARED_UNIT)) {
            //加鎖成功,判斷是否是第一個加鎖的執行緒,是則設定firstReader資訊
            //firstReader資訊將不會在threadLocals中儲存
            if (r == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
    
                //firstReader的重入情況
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                //當前執行緒是最後一個獲取讀鎖的執行緒,
                //需要將當前執行緒設定為cachedHoldCounter
                HoldCounter rh = cachedHoldCounter;
                //當前執行緒不是在此之前最後一次獲取讀鎖的執行緒
                //需要從ThreadLocals中獲取當前鎖的計數資訊
                //並且將當前執行緒設定為最後一個獲取讀鎖的執行緒
                if (rh == null || rh.tid != getThreadId(current))
                    cachedHoldCounter = rh = readHolds.get();
                //如果當前執行緒就是在此之前最後一次獲取讀鎖的資訊
                //並且鎖計數器為0,則需要設定當前執行緒的threadLcoals中儲存的鎖計數資訊
                //因為鎖計數器為0的時候會從ThreadLocals中刪除readHolds的資訊
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
            }
            return 1;
        }
        //需要被阻塞、鎖計數器超過最大值、或者cas設定鎖狀態失敗
        //進入完整版本的獲取鎖的過程
        return fullTryAcquireShared(current);
    }

    /**
     * Full version of acquire for reads, that handles CAS misses
     * and reentrant reads not dealt with in tryAcquireShared.
     */
    final int fullTryAcquireShared(Thread current) {
        /*
         * This code is in part redundant with that in
         * tryAcquireShared but is simpler overall by not
         * complicating tryAcquireShared with interactions between
         * retries and lazily reading hold counts.
         */
        HoldCounter rh = null;
        //死迴圈獲取鎖,獲取鎖的結果要麼阻塞,要麼獲取成功
        for (;;) {
            int c = getState();
                //寫鎖被獲取
            if (exclusiveCount(c) != 0) {
                    //寫鎖被其他執行緒獲取則獲取失敗
                    //寫鎖被當前執行緒獲取則可以直接獲取讀鎖,在後面處理
                if (getExclusiveOwnerThread() != current)
                    return -1;
                //如果寫鎖被當前執行緒獲取而因為嘗試獲取讀鎖阻塞,會造成死鎖
                // else we hold the exclusive lock; blocking here
                // would cause deadlock.
                
                //寫鎖沒有被獲取並且存在同步等待佇列
                //且第一個等待的節點是非當前執行緒的獲取寫鎖的節點
            } else if (readerShouldBlock()) {
                // Make sure we're not acquiring read lock reentrantly
    
                //當前執行緒是firstReader再次獲取讀鎖
                //firstReader變數存在的前提是獲取讀鎖的執行緒沒有被釋放讀鎖
                //則是一種重入的情況,可以直接判斷並增加鎖計數器
                if (firstReader == current) {
                    // assert firstReaderHoldCount > 0;
                
                //當前執行緒不是firstReader
                } else {
                    if (rh == null) {
                        //rh:當前執行緒對應的鎖計數器資訊
                        //在當前執行緒的threadLocals中儲存
                        rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current)) {
                            rh = readHolds.get();
                            //當前執行緒沒有獲取鎖,從threadLocals中移除這個鎖資訊
                            //因為readHolds.get()從當前執行緒的threadLocals中獲取HoldCounter物件時
                            //如果threadLocals中不存在當前鎖的狀態資訊,get的時候會初始化一個,count=0
                            if (rh.count == 0)
                                readHolds.remove();
                        }
                    }
                    //當前執行緒不是重入的獲取鎖
                    //並且同步等待佇列的第一個等待節點嘗試獲取寫鎖。且不失當前執行緒
                    //當前執行緒需要排隊等待
                    //目的:避免寫鎖的無限及飢餓
                    //當前執行緒已經獲取鎖
                    if (rh.count == 0)
                        return -1;
                }
            }
            //可以獲取讀鎖的情況:寫鎖被當前執行緒獲取或者重入的獲取鎖
            //或者不用阻塞寫鎖也沒有被其他執行緒獲取,到這裡的原因可能是tryAcquireShared中CAS操作失敗
            //如果是當前執行緒已經獲取樂寫鎖,則這是一個鎖降級的過程
          
            //超過讀鎖計數器的最大值
            if (sharedCount(c) == MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
    
            //cas的獲取鎖,如果cas操作失會迴圈獲取
            if (compareAndSetState(c, c + SHARED_UNIT)) {
                //如果當前執行緒是將讀鎖從0->1,則是firstReader
                if (sharedCount(c) == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                //firstReader重入的獲取鎖
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
    
                //其他重入的獲取鎖,或者滿足不阻塞條件的第一次獲取鎖
                } else {
                    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
                    cachedHoldCounter = rh; // cache for release
                }
                return 1;
            }
        }
    }

    /**
     * Performs tryLock for write, enabling barging in both modes.
     * This is identical in effect to tryAcquire except for lack
     * of calls to writerShouldBlock.
     */
    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;
    }

    /**
     * Performs tryLock for read, enabling barging in both modes.
     * This is identical in effect to tryAcquireShared except for
     * lack of calls to readerShouldBlock.
     */
    final boolean tryReadLock() {
        Thread current = Thread.currentThread();
        for (;;) {
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return false;
            int r = sharedCount(c);
            if (r == MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            if (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 true;
            }
        }
    }

    //返回當前執行緒是否持有寫鎖
    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    // Methods relayed to outer class
    //返回一個與鎖關聯的Condition
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    //獲取持有寫鎖的執行緒
    final Thread getOwner() {
        // Must read state before owner to ensure memory consistency
        return ((exclusiveCount(getState()) == 0) ?
                null :
                getExclusiveOwnerThread());
    }
    
    //獲取所有執行緒持有的總的讀鎖的數量
    final int getReadLockCount() {
        return sharedCount(getState());
    }

    final boolean isWriteLocked() {
        return exclusiveCount(getState()) != 0;
    }

    獲取當前執行緒持有的寫鎖的數量
    final int getWriteHoldCount() {
        return isHeldExclusively() ? exclusiveCount(getState()) : 0;
    }

    //獲取當前執行緒持有的讀鎖的數量
    final int getReadHoldCount() {
        if (getReadLockCount() == 0)
            return 0;

        //如果當前執行緒是firstReader則直接返回
        Thread current = Thread.currentThread();
        if (firstReader == current)
            return firstReaderHoldCount;

        //如果當前執行緒是最後一個持有讀鎖的執行緒
        HoldCounter rh = cachedHoldCounter;
        if (rh != null && rh.tid == getThreadId(current))
            return rh.count;

        //獲取當前執行緒的HoldCounter中儲存的持有鎖的數量
        int count = readHolds.get().count;
        if (count == 0) readHolds.remove();
        return count;
    }

    /**
     * Reconstitutes the instance from a stream (that is, deserializes it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        readHolds = new ThreadLocalHoldCounter();
        setState(0); // reset to unlocked state
    }

    final int getCount() { return getState(); }
}

讀鎖

讀鎖是一個支援重入的共享鎖,只要沒有超過鎖計數器的範圍,鎖可以被任意多個執行緒獲取。
鎖狀態為0且寫鎖沒由被獲取的情況下,讀鎖總能被獲取,後續的獲取操作也只是原子的增加鎖計數器。
但是由於不同執行緒都持有鎖,需要儲存每個執行緒持有這個鎖的次數,這就用到了ThreadLocalHoldCounter將執行緒的持有鎖的次數儲存在
現成的threadLocals欄位中。

讀鎖的定義

讀鎖的定義很簡單,讀鎖中也存在一個同步器,與外部類同步器保持一致,所有的操作交由同步器完成

public static class ReadLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -5992448646407690164L;
    private final Sync sync;

    /**
     * Constructor for use by subclasses
     *
     * @param lock the outer lock object
     * @throws NullPointerException if the lock is null
     */
    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    ......
}

讀鎖中的一些方法

方法名 方法描述
lock() 嘗試獲取讀鎖,根據不同的公平策略,有不同的獲取過程,忽略中斷
lockInterruptibly() 同上,會響應中斷,實現上是在獲取鎖和在佇列中從阻塞喚醒後判斷是否發生中斷,若有則丟擲中斷異常
tryLock() 搶佔式的嘗試一次獲取讀鎖,失敗則返回不會進入同步等待佇列
tryLock(long timeout, TimeUnit unit) 嘗試獲取讀鎖,會響應中斷,同時具有超時功能,實現上是在lockInterruptibly()的基礎上使用具有超時功能的LockSupport.parkNanos(Object blocker, long nanos)
unlock() 釋放一個獲取的讀鎖
newCondition() 構造一個條件等待佇列,在使用條件等待佇列阻塞前,會釋放所有的鎖

讀鎖加鎖

/**
 * Acquires the read lock.
 *
 * <p>Acquires the read lock if the write lock is not held by
 * another thread and returns immediately.
 *
 * <p>If the write lock is held by another thread then
 * the current thread becomes disabled for thread scheduling
 * purposes and lies dormant until the read lock has been acquired.
 */
//申請讀鎖
//如果寫鎖沒有被其他執行緒持有,則立刻獲得讀鎖並返回
//如果寫鎖沒有被其他執行緒持有,處於執行緒排程的目的,當前執行緒會進入waiting狀態直到獲取到鎖
public void lock() {
    sync.acquireShared(1);
}

acquireShared()方法:至少會呼叫一次同步器的tryAcquireShared()方法

/**
 * Acquires in shared mode, ignoring interrupts.  Implemented by
 * first invoking at least once {@link #tryAcquireShared},
 * returning on success.  Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquireShared} until success.
 *
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquireShared} but is otherwise uninterpreted
 *        and can represent anything you like.
 */
//以忽略中斷、共享模式獲取鎖,至少會呼叫一次tryAcquireShared方法,成功獲取鎖則返回1,
//失敗則返回-1,執行緒會進入同步等待佇列,滿足條件時會被阻塞直到前一個執行緒(寫鎖)釋放或者(讀鎖)被獲取
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
//tryAcquireShared方法由同步器重寫獲取過程,是真正的獲取鎖的過程
//doAcquireShared中獲取鎖的步驟依然是交給tryAcquireShared完成,自身更多完成排隊等待的動作

tryAcquireShared()方法

protected final int tryAcquireShared(int unused) {
    /*
     * 待辦事項:
     * 1. 如果寫鎖被其他執行緒獲取,則失敗.
     * 2. 否則這個執行緒就有資格使用鎖的狀態,因此需要判斷是否因為
     *    因為同步等待策略而阻塞,否則嘗通過cas操作嘗試授予鎖
     *    可重入性的操作在fullTryAcquireShared中進行
     *    避免在不可重入的情況下檢查鎖狀態
     * 3. 如果步驟2失敗,因為執行緒不符合條件或者cas失敗
     *    則進入fullTryAcquireShared中迴圈重試
     */
    Thread current = Thread.currentThread();
    int c = getState();
        //寫鎖被其他執行緒持有則獲取讀鎖失敗,需要進入同步等待佇列
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
        
        /**
         * 判斷當前執行緒是否需要阻塞
         * 不同的公平策略有不同的判斷方式
         * 非公平模式下,如果存在同步等待佇列且第一個是嘗試獲取寫鎖的
         * 其他執行緒則需要阻塞
         * 公平模式下,佇列中存在排隊等待的執行緒則需要進入佇列等待
         */

        //如果當前執行緒已經獲取樂寫鎖,則這可以是一個鎖降級的過程
        //不用進入佇列排隊
    if (!readerShouldBlock() &&
        //鎖的獲取次數不能超過最大的可獲取的次數
        r < MAX_COUNT &&
        //不需要阻塞,鎖的計數沒有超過最大值則嘗試通過cas操作加鎖
        //可能會失敗,如果存在多個執行緒同時競爭
        compareAndSetState(c, c + SHARED_UNIT)) {
        //加鎖成功,判斷是否是第一個加鎖的執行緒,是則設定firstReader資訊
        //firstReader資訊將不會在threadLocals中儲存
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;

            //firstReader的重入情況
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            //當前執行緒是最後一個獲取讀鎖的執行緒,
            //需要將當前執行緒設定為cachedHoldCounter
            HoldCounter rh = cachedHoldCounter;
            //當前執行緒不是在此之前最後一次獲取讀鎖的執行緒
            //需要從ThreadLocals中獲取當前鎖的計數資訊
            //並且將當前執行緒設定為最後一個獲取讀鎖的執行緒
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            //如果當前執行緒就是在此之前最後一次獲取讀鎖的資訊
            //並且鎖計數器為0,則需要設定當前執行緒的threadLcoals中儲存的鎖計數資訊
            //因為鎖計數器為0的時候會從ThreadLocals中刪除readHolds的資訊
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    //需要被阻塞、鎖計數器超過最大值、或者cas設定鎖狀態失敗
    //進入完整版本的獲取鎖的過程
    return fullTryAcquireShared(current);
}

fullTryAcquireShared()方法

/**
 * 獲取讀鎖操作的完整版本,處理CAS遺漏和可重入讀操作,
 * tryAcquireShared中沒有處理
 */

final int fullTryAcquireShared(Thread current) {
    /*
     * This code is in part redundant with that in
     * tryAcquireShared but is simpler overall by not
     * complicating tryAcquireShared with interactions between
     * retries and lazily reading hold counts.
     */
    HoldCounter rh = null;
    //死迴圈獲取鎖,獲取鎖的結果要麼阻塞,要麼獲取成功
    for (;;) {
        int c = getState();
            //寫鎖被獲取
        if (exclusiveCount(c) != 0) {
                //寫鎖被其他執行緒獲取則獲取失敗
                //寫鎖被當前執行緒獲取則可以直接獲取讀鎖,在後面處理
            if (getExclusiveOwnerThread() != current)
                return -1;
            //如果寫鎖被當前執行緒獲取而因為嘗試獲取讀鎖阻塞,會造成死鎖
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
            
            //寫鎖沒有被獲取並且存在同步等待佇列
            //且第一個等待的節點是非當前執行緒的獲取寫鎖的節點
        } else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly

            //當前執行緒是firstReader再次獲取讀鎖
            //firstReader變數存在的前提是獲取讀鎖的執行緒沒有被釋放讀鎖
            //則是一種重入的情況,可以直接判斷並增加鎖計數器
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            
            //當前執行緒不是firstReader
            } else {
                if (rh == null) {
                    //rh:當前執行緒對應的鎖計數器資訊
                    //在當前執行緒的threadLocals中儲存
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        //當前執行緒沒有獲取鎖,從threadLocals中移除這個鎖資訊
                        //因為readHolds.get()從當前執行緒的threadLocals中獲取HoldCounter物件時
                        //如果threadLocals中不存在當前鎖的狀態資訊,get的時候會初始化一個,count=0
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                //當前執行緒不是重入的獲取鎖
                //並且同步等待佇列的第一個等待節點嘗試獲取寫鎖。且不失當前執行緒
                //當前執行緒需要排隊等待
                //目的:避免寫鎖的無限及飢餓
                //當前執行緒已經獲取鎖
                if (rh.count == 0)
                    return -1;
            }
        }
        //可以獲取讀鎖的情況:寫鎖被當前執行緒獲取或者重入的獲取鎖
        //或者不用阻塞寫鎖也沒有被其他執行緒獲取,到這裡的原因可能是tryAcquireShared中CAS操作失敗
        //如果是當前執行緒已經獲取樂寫鎖,則這是一個鎖降級的過程
      
        //超過讀鎖計數器的最大值
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");

        //cas的獲取鎖,如果cas操作失會迴圈獲取
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            //如果當前執行緒是將讀鎖從0->1,則是firstReader
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            //firstReader重入的獲取鎖
            } else if (firstReader == current) {
                firstReaderHoldCount++;

            //其他重入的獲取鎖,或者滿足不阻塞條件的第一次獲取鎖
            } else {
                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
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

讀鎖解鎖

/**
 * Attempts to release this lock.
 *
 * <p>If the number of readers is now zero then the lock
 * is made available for write lock attempts.
 */
//釋放讀鎖
public void unlock() {
    sync.releaseShared(1);
}

releaseShared()方法,在AQS中實現

/**
 * Releases in shared mode.  Implemented by unblocking one or more
 * threads if {@link #tryReleaseShared} returns true.
 *
 * @param arg the release argument.  This value is conveyed to
 *        {@link #tryReleaseShared} but is otherwise uninterpreted
 *        and can represent anything you like.
 * @return the value returned from {@link #tryReleaseShared}
 */
//釋放共享鎖,
//這一步需要完成兩個任務:1-釋放當前執行緒的讀鎖,
//2-如果讀鎖不再被任何執行緒持有則喚醒同步等待佇列中等待的節點(如果有)
//如果tryReleaseShared返回的是true,則解除同步等待佇列中的一個或者多個阻塞的節點
//tryReleaseShared返回true代表讀鎖不再被任何執行緒持有
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

tryReleaseShared()方法

//嘗試釋放共享鎖,與加共享鎖一致,只能一個一個的釋放
//unused引數沒有被使用
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    //當前執行緒是firstReader並且僅持有一次讀鎖,在釋放讀鎖後firstReader應該置null
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        //獲取當前執行緒的HoldCounter資訊
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        //當讀誦持有數量歸零時,會從執行緒的threadLocals中刪除readHolds
        if (count <= 1) {
            readHolds.remove();
            //沒持有鎖的縣城不能釋放鎖
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    //使用cas操作原子的減少鎖狀態,避免CAS操作失敗的情況
    for (;;) {
        int c = getState();
        //減少一個讀鎖
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // 釋放讀鎖對於讀操作沒有影響,
            // 但是如果現在讀鎖和寫鎖都是空閒的
            // 可能會使等待的獲取寫鎖的操作繼續
            
            //返回鎖是否還被任何一個執行緒持有
            return nextc == 0;
    }
}

doReleaseShared()方法,在AQS中實現,用於喚醒佇列中的後繼節點,
這個方法的出口需要一些解釋,具體內容參看doReleaseShared()

寫鎖

寫鎖是一個支援重入的排他鎖,佇列的操作與可重入鎖相同,可以作為參考,區別是獲取鎖的過程和進入佇列的條件

寫鎖的定義

寫鎖的變臉與讀鎖相同,僅有一個同步器欄位,所有的操作呼叫同步器對應的方法完成

public static class WriteLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -4992448646407690164L;
    private final Sync sync;

    /**
     * Constructor for use by subclasses
     *
     * @param lock the outer lock object
     * @throws NullPointerException if the lock is null
     */
    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    .......
}

寫鎖的一些方法

方法名 方法描述
lock() 嘗試獲取寫鎖,根據不同的公平策略,有不同的獲取過程,忽略中斷
lockInterruptibly() 同上,會響應中斷,實現上是在獲取鎖和在佇列中從阻塞喚醒後判斷是否發生中斷,若有則丟擲中斷異常
tryLock() 搶佔式的嘗試一次獲取一次寫鎖,失敗則返回不會進入同步等待佇列
tryLock(long timeout, TimeUnit unit) 嘗試獲取寫鎖,會響應中斷,同時具有超時功能,實現上是在lockInterruptibly()的基礎上使用具有超時功能的LockSupport.parkNanos(Object blocker, long nanos)
unlock() 釋放一個獲取的寫鎖
newCondition() 構造一個條件等待佇列,在使用條件等待佇列阻塞前,會釋放所有的鎖
isHeldByCurrentThread() 判斷當前執行緒是否持有寫鎖
getHoldCount 獲取當前執行緒重入性獲取當前鎖的次數

寫鎖得加鎖

寫鎖是一個排他鎖,總體思路感覺可以說是可重入鎖和讀鎖得結合,寫鎖獲取成功時要求讀鎖沒有被獲取並且寫鎖沒有被其他執行緒獲取,
類似於可重入鎖,一定是獨佔的。考慮到讀寫鎖是一對鎖,因此在加鎖的時候肯定是需要判斷讀鎖有沒有被獲取。
讀寫鎖任意一個被其他執行緒獲取,都將導致執行緒進入同步等待佇列等候。

加鎖:

/**
 * Acquires the write lock.
 *
 * <p>Acquires the write lock if neither the read nor write lock
 * are held by another thread
 * and returns immediately, setting the write lock hold count to
 * one.
 *
 * <p>If the current thread already holds the write lock then the
 * hold count is incremented by one and the method returns
 * immediately.
 *
 * <p>If the lock is held by another thread then the current
 * thread becomes disabled for thread scheduling purposes and
 * lies dormant until the write lock has been acquired, at which
 * time the write lock hold count is set to one.
 */
//嘗試獲取寫鎖,如果讀鎖和寫鎖都沒有被其他執行緒持有,則可以獲取寫鎖。
//如果當前執行緒已經獲取了寫鎖,則鎖計數器加一併返回
//如果鎖由其他執行緒持有,那麼執行緒將入同步等待佇列阻塞,直到可以獲取鎖
public void lock() {
    sync.acquire(1);
}

acquire()方法,在AQS中實現,這個就和可重入鎖的一樣了,由AQS完成佇列的操作

/**
 * Acquires in exclusive mode, ignoring interrupts.  Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success.  Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquire} until success.  This method can be used
 * to implement method {@link Lock#lock}.
 *
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 */
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire()方法,實際獲取寫鎖的方法,這份方法在Sync中實現,這裡需要解釋一個問題:

  • 在獲取寫鎖的時候,若之前已經獲取了讀鎖,則一定要釋放讀鎖再獲取讀鎖。如果不釋放讀鎖,則可能造成死鎖。
    舉個例子:假設有ABCD四個執行緒獲取了讀鎖且沒釋放,此時執行緒A嘗試獲取寫鎖。由於讀鎖被執行緒獲取,執行緒A將進入同步等待佇列被阻塞,
    此時執行緒A讀鎖依然沒有被釋放,讀鎖釋放時喚醒後繼節點的條件是讀鎖不被任何執行緒獲取,此時讀鎖依然被A持有,且A在阻塞。
    由於執行緒A在同步等待佇列的隊頭且獲取寫鎖,則之後所有的獲取鎖的執行緒都將被阻塞。至此死鎖。
//嘗試獲取寫鎖
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.
     */
    /*
     * 需要完成的工作:
     * 1. 如果鎖狀態(包含讀鎖和寫鎖)不為0,並且當前執行緒沒有持有寫鎖則失敗
     * 2. 如果寫鎖計數器大於最大值則獲取失敗
     * 3. 否則如果是重入的獲取鎖,則會被允許.
     */
    Thread current = Thread.currentThread();
    //獲取鎖狀態
    int c = getState();
    //獲取寫鎖的鎖狀態
    int w = exclusiveCount(c);
    //如果鎖被不為null即可能被任何一個執行緒持有
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        //w==0則讀鎖被某個執行緒持有或者寫鎖被其他執行緒持有則獲取鎖失敗
        //進入佇列排隊
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;

        //寫所已經被當前執行緒持有則判斷再次加鎖後是否會超過寫鎖的最大可以被加鎖的次數
        //超過則加鎖失敗
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        //重入的獲取鎖
        setState(c + acquires);
        return true;
    }
    //鎖沒有被任何執行緒持有,則需要根據公平策略來判斷當前執行緒是否需要阻塞
    //公平鎖:檢查同步等待佇列,若佇列中存在等待時間更長的執行緒則需要阻塞
    //非公平鎖:可以搶佔式獲取寫鎖,不需要阻塞

    //需要被阻塞或者CAs操作失敗則進入同步佇列
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    //不需要阻塞並且加鎖成功,設定排他鎖的所屬執行緒資訊
    setExclusiveOwnerThread(current);
    return true;
}

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))與可重入鎖完全相同,可以參考這裡

讀寫鎖的一些其他方法

方法名 方法描述
isFair() 判斷鎖是否使用的是公平模式
getOwner() 獲取持有寫鎖的執行緒
getReadLockCount() 獲取讀鎖被所有執行緒持有的次數
isWriteLocked() 判斷寫鎖是否被任意一個執行緒獲取
isWriteLockedByCurrentThread() 判斷當前執行緒是否持有寫鎖
getWriteHoldCount() 獲取當前執行緒重入的獲取寫鎖的次數
getReadHoldCount() 獲取當前執行緒重入的獲取讀鎖的次數
getQueuedWriterThreads() 獲取在同步等待佇列中等待的獲取寫鎖的執行緒集合
getQueuedReaderThreads() 獲取在同步等待佇列中等待的獲取讀鎖的執行緒集合
hasQueuedThreads() 判斷同步等待佇列中是否存在節點(等待的執行緒)
hasQueuedThread(Thread thread) 判斷給定的執行緒是否在佇列中
getQueueLength() 獲取同步等待佇列中等待的節點個數
getQueuedThreads() 獲取在同步等待佇列中等待的執行緒集合
hasWaiters(Condition condition) 判斷給定的條件等待佇列中是否存在等待的節點
getWaitQueueLength(Condition condition) 判斷給定的條件等待佇列中是否存在等待的節點個數
getWaitingThreads(Condition condition) 獲取給定的條件等待佇列中是否存在等待的節點集合

總結

  • 讀寫鎖把對物件的操作劃分為讀操作和寫操作,讀操作通過獲取讀鎖只能讀取物件,寫操作通過獲取寫鎖
    來完成寫操作的排他性。這種鎖相對於排他鎖而言具有更高的併發性。
  • 已經獲取寫鎖的執行緒可以通過鎖降級來獲取讀鎖
  • 讀寫鎖是一個鎖,只是可以進行共享或者排他的兩種操作模式。
  • 如果一個鎖既沒有加寫鎖,也沒有加讀鎖,佇列中不存在等待的執行緒,則可以被任意一個執行緒獲取任意一個鎖
  • 一個鎖被加了讀鎖,則不能有任何執行緒加寫鎖
  • 一個鎖被加了寫鎖,加寫鎖的執行緒可以再次加讀鎖
  • 非公平模式下,同步佇列的第一個等待節點是獲取寫鎖,則之後的獲取讀鎖的執行緒需要等待
  • 公平模式下,只要佇列中存在等待的節點就需要進入佇列等待
  • 讀寫鎖不支援鎖升級

\(\color{#FF3030}{轉載請標明出處}\)

附ReentrantReadWriteLock全部原始碼理解:https://www.cnblogs.com/bmilk/p/13044867.html

相關文章