ReentrantReadWriteLock

前言

之前分析了使用AQS實現的共享鎖和獨佔鎖,今天來分析一下ReentrantReadWriteLock,這個即使用了共享鎖(讀鎖)又使用了獨享鎖(寫鎖)的類。

與Mysql中的S鎖(共享鎖,讀鎖)一樣,ReentrantReadWriteLock中的讀鎖只允許繼續加讀鎖,而不允許加寫鎖。

而寫鎖則與Mysql中X鎖(排他鎖)一樣,不允許繼續加任何鎖,知道寫鎖被釋放。

今天我們就來分析下ReentrantReadWriteLock是如何做到的。

ReentrantReadWriteLock 源碼分析

  • 構造方法

    ReentrantReadWriteLock 支持公平與非公平模式, 這點和ReentrantLock一樣,構造函數中可以通過指定的值傳遞進去。ReentrantReadWriteLock 顧名思義,可重入的讀寫鎖。

/**
 * Creates a new {@code KReentrantReadWriteLock} with
 * default (nonfair) ordering properties
 * 用 nonfair 來構建 read/WriteLock (這裏的 nonfair 指的是當進行獲取 lock 時 若 aqs的syn queue 裏面是否有 Node 節點而決定所採取的的策略)
 */
public ReentrantReadWriteLock(){
    this(false);
}

/**
 *  構建 ReentrantReadLock
 */
public ReentrantReadWriteLock(boolean fair){
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

    /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;

和ReentrantLock一樣,是否是公平鎖是看獲取鎖的策略,而策略的實現要看內部類Sync的具體實現。

ReentrantReadWriteLock的構造方法中,除了創建一個內部類Sync對象,還創建了內部類ReadLock對象以及內部類WriteLock對象。

ReadLock構造方法

    private final Sync sync;
  protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

ReadLock構造方法將ReentrantReadWriteLock的sync屬性賦給ReadLock的sync屬性。

WriteLock構造方法

 
     private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

WriteLock構造方法將ReentrantReadWriteLock的sync屬性賦給WriteLock的sync屬性。

我們看到ReadLock與WriteLock都有sync屬性,而且使用的是同一個Sync對象。

  • Sync中一些關鍵的屬性

    • 讀寫計數器

      /**
       * ReentrantReadWriteLock 使用 AQS裏面的 state的高低16位來記錄 read /write 獲取的次數(PS: writeLock 是排他的 exclusive, readLock 是共享的 shared )
       * 記錄的操作都是通過 CAS 操作(有競爭發生)
       *
       *  特點:
       *      1) 同一個線程可以擁有 writeLock 與 readLock (但必須先獲取 writeLock 再獲取 readLock, 反過來進行獲取會導致死鎖)
       *      2) writeLock 與 readLock 是互斥的(就像 Mysql 的 X S 鎖)
       *      3) 在因 先獲取 readLock 然後再進行獲取 writeLock 而導致 死鎖時, 本線程一直卡住在對應獲取 writeLock 的代碼上(因爲 readLock 與 writeLock 是互斥的, 在獲取 writeLock 時監測到現在有線程獲取 readLock , 鎖一會一直在 aqs 的 sync queue 裏面進行等待), 而此時
       *          其他的線程想獲取 writeLock 也會一直 block, 而若獲取 readLock 若這個線程以前獲取過 readLock, 則還能繼續 重入 (reentrant), 而沒有獲取 readLock 的線程因爲 aqs syn queue 裏面有獲取 writeLock 的 Node 節點存在會存放在 aqs syn queue 隊列裏面 一直 block
       */
      
      /** 對 32 位的 int 進行分割 (對半 16) */
      static final int SHARED_SHIFT   = 16;
      static final int SHARED_UNIT    = (1 << SHARED_SHIFT); // 000000000 00000001 00000000 00000000
      static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; // 000000000 00000000 11111111 11111111
      static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 000000000 00000000 11111111 11111111
      
      /** Returns the number of shared holds represented in count */
      /** 計算 readLock 的獲取次數(包含重入的次數) */
      static int sharedCount(int c)       { return c >>> SHARED_SHIFT; } // 將字節向右移動 16位, 只剩下 原來的 高 16 位
      /** Returns the number of exclusive holds represented in count */
      /** 計算 writeLock 的獲取的次數(包括重入的次數) */
      static int exclusiveCount(int c)    { return c & EXCLUSIVE_MASK; } // 和EXCLUSIVE_MASK 與 也就是隻取低16位
      
      
      

      之前我們說過了讀寫鎖使用的是同一個Sync對象,之前我們分析的獨佔鎖或者共享鎖,都是通過判斷state狀態來完成獲取鎖或者釋放鎖的邏輯的,那麼這裏有獨佔鎖和共享鎖共同存在,依然還是需要state這個一個字段來判斷獲取鎖或釋放鎖的邏輯(我們依舊需要使用一個字段來判斷,因爲讀鎖和寫鎖不是完全孤立的,寫鎖會阻塞寫鎖和讀鎖,讀鎖會阻塞寫鎖,所以用兩個字段來判斷,反而會增加處理複雜度),需要怎樣做呢?Doug Lea大師給出的答案是,state的高16位來記錄 read 獲取的次數,低16位來記錄write獲取的次數,具體的實現非常精彩,通過位操作,巧妙的使用state字段來標識了讀鎖和寫鎖的關係。

    • 線程獲取讀鎖次數統計相關屬性

      /**
      - A counter for per-thread read hold counts
      - Maintained as a ThreadLocal; cached in cachedHoldCounter
        */
        /**
      - 幾乎每個獲取 readLock 的線程都會含有一個 HoldCounter 用來記錄 線程 id 與 獲取 readLock 的次數 ( writeLock 的獲取是由 state 的低16位 及 AQS中的exclusiveOwnerThread 來進行記錄)
      - 這裏有個注意點 第一次獲取 readLock 的線程使用 firstReader, firstReaderHoldCount 來進行記錄
      - (PS: 不對, 我們想一下爲什麼不 統一用 HoldCounter 來進行記錄呢? 原因: 所有的 HoldCounter 都是放在 ThreadLocal 裏面, 而很多有些場景中只有一個線程獲取 readLock 與 writeLock , 這種情況還用 ThreadLocal 的話那就有點浪費(ThreadLocal.get() 比直接 通過 reference 來獲取數據相對來說耗性能))
        */
        static final class HoldCounter {
        int count = 0; // 重複獲取 readLock/writeLock 的次數
        // Use id, not reference, to avoid garbage retention
        final long tid = getThreadId(Thread.currentThread()); // 線程 id
        }
      
      /**
      
      - ThreadLocal subclass, Easiest to explicitly define for sake
      - of deserialization mechanics
        */
        /** 簡單的自定義的 ThreadLocal 來用進行記錄  readLock 獲取的次數  */
        static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter>{
        @Override
        protected 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
        */
        /**
      - readLock 獲取記錄容器 ThreadLocal(ThreadLocal 的使用過程中當 HoldCounter.count == 0 時要進行 remove , 不然很有可能導致 內存的泄露)
        */
        private transient ThreadLocalHoldCounter readHolds;
      
      /**
      
      - 最後一次獲取 readLock 的 HoldCounter 的緩存
      - (PS: 還是上面的問題 有了 readHolds 爲什麼還需要 cachedHoldCounter呢? 在非常多的場景中, 這次進行release readLock的線程就是上次 acquire 的線程, 這樣直接通過cachedHoldCounter來進行獲取, 節省了通過 readHolds 的 lookup 的過程)
        */
        private transient HoldCounter cachedHoldCounter;
      
      /**
      
      - 下面兩個是用來進行記錄 第一次獲取 readLock 的線程的信息
      - 準確的說是第一次獲取 readLock 並且 沒有 release 的線程, 一旦線程進行 release readLock, 則 firstReader會被置位 null
        */
        private transient Thread firstReader = null;
        private transient int    firstReaderHoldCount;
      
      
      • ThreadLocalHoldCounter繼承了ThreadLocal,一個存儲HoldCounter類型對象的ThreadLocal實例,並且重寫了initialValue()方法,這意味着,當調用get()方法時,如果之前沒有設置值,將會調用initialValue()生成value(key爲當前ThreadLocal實例,一個ThreadLocalHoldCounter對象)放入到當前線程的ThreadLocalMap中。
      • HoldCounter 用來記錄當前線程獲取讀鎖次數的一個類
  • ReadLock 讀鎖的實現

    • ReadLock#lock

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

      同樣是直接調用AQS的final方法acquireShared(),直接看定義何時成功獲到共享鎖的方法的具體實現。以非公平鎖爲例。

      Sync#tryAcquireShared()

        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();
               //state低16位標識寫鎖,查看是否存在寫鎖
                  if (exclusiveCount(c) != 0 &&
                      //查看當前佔有AQS的線程是否是當前線程
                      getExclusiveOwnerThread() != current)
                      //如果條件都不滿足,則返回-1,當前線程進入自旋嘗試獲取共享鎖,自旋獲取是則被掛起
                      return -1;
            //取高16位讀鎖,查看擁有讀鎖個數 
                  int r = sharedCount(c);
               	//readerShouldBlock()是Sync這個抽象類中的抽象方法。在NonFairSync和FairSync中有不同的實現 ,如果這個方法返回true的話就會導致fullTryAcquireShared的執行
                  if (!readerShouldBlock() &&
                      r < MAX_COUNT &&
                      //讀鎖加1,我們看到讀鎖加1會將state+2^16,這就導致了state的高16位代表讀鎖個數
                      compareAndSetState(c, c + SHARED_UNIT)) {
                      //進入這塊邏輯,會返回1,不會去嘗試自旋獲取共享歐鎖,而是結束方法,線程繼續往下執行
                      if (r == 0) {
                          firstReader = current;
                          firstReaderHoldCount = 1;
                      }
                      //如果當前線程是重入的,第一次讀的線程就是當前線程
                      else if (firstReader == current) {
                          firstReaderHoldCount++;
                      } else {
                         // 非 firstReader 讀鎖重入計數更新
      
                          HoldCounter rh = cachedHoldCounter;
                          //cachedHoldCounter爲null或者當前線程id不是cachedHoldCounter的線程id
                          if (rh == null || rh.tid != getThreadId(current))
                             
                              cachedHoldCounter = rh = readHolds.get();
                          else if (rh.count == 0)
                              readHolds.set(rh);
                          rh.count++;
                      }
                      return 1;
                  }
                 //第一次獲取讀鎖失敗,有兩種情況:
         //1)沒有寫鎖被佔用時,嘗試通過一次CAS去獲取鎖時,更新失敗(說明有其他讀鎖在申請)
        //2)當前線程佔有寫鎖,並且有其他寫鎖在當前線程的下一個節點等待獲取寫鎖,除非當前線程的下一個節點被取消,否則fullTryAcquireShared也獲取不到讀鎖\
             //代碼調用 fullTryAcquireShared 大體情況是 AQS 的 sync queue 裏面有其他的節點 或 AQS queue 的 head.next 是個獲取 writeLock 的節點, 或 CAS 操作 state 失敗
                  return fullTryAcquireShared(current);
              }
      

      1.如果存在寫鎖,並且持有寫鎖的線程不是當前線程,則返回-1,當前線程進入自旋獲取鎖的過程。

      2.如果當前獲取讀鎖的操作不需要被阻塞並且CAS增加讀鎖計數成功且沒有達到讀鎖個數限制,進入記錄當前線程 獲取 readLock 的次數的邏輯,最後返回1,成功獲取到讀鎖。

      3.如果前兩種情況都不符合,那麼就執行fullTryAcquireShared方法。

      2中當前獲取讀鎖的操作是否需要被阻塞時,公平鎖和非公平鎖的實現不同

      NonFairSync#readerShouldBlock()

         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;
          }
      
      

      我們看到NonFairSync類中對是否需要阻塞讀鎖的實現實際上調用的是AQS中的方法,其實就是查看第一個線程Node節點是獨佔類型節點(即是寫鎖Node節點),如果是的話,就需要阻塞讀。

      FairSync#readerShouldBlock()

        final boolean readerShouldBlock() {
                  return hasQueuedPredecessors();
              }
      
         public final boolean hasQueuedPredecessors() {
              // The correctness of this depends on head being initialized
              // before tail and on head.next being accurate if the current
              // thread is first in queue.
              Node t = tail; // Read fields in reverse initialization order
              Node h = head;
              Node s;
             //如果隊列中存在線程Node節點,
              return h != t &&
                  //第一個線程Node節點不是當前節點
                  ((s = h.next) == null || s.thread != Thread.currentThread());
          }
      
      

      如果隊列中存在線程Node節點且點一個線程Node節點不是當前線程,那麼就要阻塞當前讀。

      2中記錄當前線程 獲取 readLock 的次數的邏輯分析

         if (r == 0) {
                          firstReader = current;
                          firstReaderHoldCount = 1;
                      }
                      //如果當前線程是重入的,第一次讀的線程就是當前線程
                      else if (firstReader == current) {
                          firstReaderHoldCount++;
                      } else {
                         // 非 firstReader 讀鎖重入計數更新
      
                          HoldCounter rh = cachedHoldCounter;
                          //cachedHoldCounter爲null或者當前線程id不是cachedHoldCounter的線程id
                          if (rh == null || rh.tid != getThreadId(current))
                             
                              cachedHoldCounter = rh = readHolds.get();
                          else if (rh.count == 0)
                              readHolds.set(rh);
                          rh.count++;
                      }
                      return 1;
      

      1.如果當前讀鎖計數爲0,那麼將當前線程置爲firstReader線程,firstReaderHoldCount置爲1.

      2.如果當前線程是重入的,第一次讀的線程就是當前線程,之前將 firstReaderHoldCount++;

      3.否則,找到之前緩存的cachedHoldCounter。

      3.1如果緩存的HoldCounter爲空或者緩存HoldCounter不是屬於當前線程的HoldCounter。那麼就將 cachedHoldCounter置爲當前線程的HoldCounter。

      3.2 如果緩存的HoldCounter屬於當前線程,如果當前線程的讀鎖計算爲0,那麼就將cachedHoldCounter設置爲當前線程的HoldCounter。這個時候爲0的情況,只可能是釋放共享鎖的方法調用了readHolds.remove();將當前線程的ThreadLocalMap中以readHolds爲key的Entry刪除掉,所以這裏需要重置。

      3.3 3.1或3.2執行完後均將HoldCounter的count+1。

      fullTryAcquireShared

      /**
       * Full version of acquire for reads, that handles CAS misses
       * and reentrant reads not dealt with in tryAcquireShared.
       */
      /**
       *  fullTryAcquireShared 這個方法其實是 tryAcquireShared 的冗餘(redundant)方法, 主要補足 readerShouldBlock 導致的獲取等待 和 CAS 修改 AQS 中 state 值失敗進行的修補工作
       */
      final int fullTryAcquireShared(Thread current){
          /**
           * This code is 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)           // 1. 若此刻 有其他的線程獲取了 writeLock 則當前線程要進入自旋獲取讀鎖的過程,宿命大概就是被掛起了
                      return -1;
                  // else we hold the exclusive lock; blocking here
                  // would cause deadlock
              }else if(readerShouldBlock()){                        // 2. 判斷 獲取 readLock 的策略
                  // Make sure we're not acquiring read lock reentrantly
                  if(firstReader == current){                       // 3. 若是 readLock 的 重入獲取, 則直接進行下面的 CAS 操作
                      // assert firstReaderHoldCount > 0
                  }else{
                      if(rh == null){
                          rh = cachedHoldCounter;
                          if(rh == null || rh.tid != getThreadId(current)){
                              rh = readHolds.get();
                              if(rh.count == 0){
                                  readHolds.remove();               // 4. 若 rh.count == 0 進行 ThreadLocal.remove
                              }
                          }
                      }
                      if(rh.count == 0){                            // 5.  count != 0 則說明這次是 readLock 獲取鎖的 重入(reentrant), 所以即使出現死鎖, 以前獲取過 readLock 的線程還是能繼續 獲取 readLock
                          return -1;                                // 6. 進行到這一步只有 當 aqs sync queue 裏面有 獲取 readLock 的node 或 head.next 是獲取 writeLock 的節點
                      }
                  }
              }
      
              if(sharedCount(c) == MAX_COUNT){                      // 7. 是否獲取 鎖溢出
                  throw new Error("Maximum lock count exceeded");
              }
              if(compareAndSetState(c, c + SHARED_UNIT)){          // 8.  CAS 可能會失敗, 但沒事, 我們這邊外圍有個 for loop 來進行保證 操作一定進行
                  if(sharedCount(c) == 0){                         //  9. r == 0 沒有線程獲取 readLock 直接對 firstReader firstReaderHoldCount 進行初始化
                      firstReader = current;
                      firstReaderHoldCount = 1;
                  }else if(firstReader == current){                // 10. 第一個獲取 readLock 的是 current 線程, 直接計數器加 1
                      firstReaderHoldCount++;
                  }else{
                      if(rh == null){
                          rh = cachedHoldCounter;
                      }
                      if(rh == null || rh.tid != getThreadId(current)){
                          rh = readHolds.get();                    // 11. 還是上面的邏輯, 先從 cachedHoldCounter, 數據不對的話, 再從readHolds拿數據
                      }else if(rh.count == 0){
                          readHolds.set(rh);                       // 12. 爲什麼要 count == 0 時進行 ThreadLocal.set? 因爲上面 tryReleaseShared方法 中當 count == 0 時, 進行了ThreadLocal.remove
                      }
                      rh.count++;
                      cachedHoldCounter = rh; // cache for release // 13. 獲取成功
                  }
                  return 1;
              }
      
          }
      }
      
      
      

      1.如果寫鎖存在,並且佔有寫鎖的線程不是當前線程,那麼直接返回-1,當前線程將進入自旋獲取讀鎖,宿命大概就是被掛起。

      2.否則進行是否需要阻塞讀的判斷

      2.1 如果需要阻塞讀

      2.1.1如果首次獲取讀鎖的線程是當前線程,不做處理

      2.1.2 否則

      2.1.2.1 如果rh這個局部變量爲null,那麼將緩存HoldCounter指向該變量。如果緩存HoldCounter所屬的線程不是當前線程,那麼吧rh指向當前線程的HoldCounter,這時,如果rh.count依舊爲0,則remove掉當前線程的HoldCounter。

      2.1.2.2 如果rh這個局部變量不爲null,如果rh.count爲0,那麼返回-1.

      3.如果之前的操作都沒有導致return退出循環,那麼先判斷讀鎖計數器是否溢出,溢出拋出異常。

      4.CAS設置新的讀鎖計數器,通過不斷的循環來保證成功。然後開始增加當前線程 獲取 readLock 的次數,最後返回1,結束獲取讀鎖的方法。

    • ReadLock#unlock

          public void unlock() {
                  sync.releaseShared(1);
              }
      

      也是直接使用了AQS的final方法releaseShared(),我們直接看tryReleaseShared()方法。

         protected final boolean tryReleaseShared(int unused) {
               Thread current = Thread.currentThread();
          //1.如果當前線程就是首次獲取讀鎖的線程
               if (firstReader == current) {
                   // assert firstReaderHoldCount > 0;
                   //1.1如果當前線程的讀重入次數爲1,將首次獲取讀鎖的線程置爲空
                   if (firstReaderHoldCount == 1)
                       firstReader = null;
                   //1.2否則只是將讀重入次數減少一次
                   else
                       firstReaderHoldCount--;
               } 
          //2.如果不是
          else {
              //獲取上一次獲取讀鎖的線程的HoldCounter
                   HoldCounter rh = cachedHoldCounter;
              //2.1如果緩存的HoldCounter爲空或者當前線程不是之前緩存的HoldCounter所屬的線程
                   if (rh == null || rh.tid != getThreadId(current))
                       //獲取當前線程的HoldCounter 如果之前沒有,側會實例化一個新的
                       rh = readHolds.get();
                   int count = rh.count;
              //2.2當重入次數<=1的時候,我們需要清除掉當前線程的HoldCounter
                   if (count <= 1) {
                       //這裏會remove();防止內存泄漏
                       readHolds.remove();
                       if (count <= 0)
                           throw unmatchedUnlockException();
                   }
              //重入次數-1
                   --rh.count;
               }
          //3.讀鎖減一
               for (;;) {
                   //獲取當前的state
                   int c = getState();
                   //將讀鎖計數器計數減一
                   int nextc = c - SHARED_UNIT;
                   //CAS設置新的state值,直到成功才退出循環
                   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,如果爲0返回true,否則就返回false
                       return nextc == 0;
               }
           }
    

    1.對線程的讀鎖重入次數進行減一操作

​ 1.1如果當前線程就是首次獲取讀鎖的線程,那麼對firstReader和firstReaderHoldCount進行操作。

​ 1.2如果不是,那麼對cachedHoldCounter進行操作,如果當前線程與之前緩存HoldCounter所屬線程不一

​ 致,需要獲取到屬性當前線程的HoldCounter(如果當前線程沒有HoldCounter,需要實例化一個),然後

​ 進行讀鎖重入次數減一的操作。

​ 2.使用循環CAS完成對讀鎖計數減一,當讀鎖計數爲0的時候,喚醒阻塞的線程。

  • WriteLock寫鎖的實現

    • WriteLock.lock()

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

      依舊是調用AQS的final方法acquire()方法,我們直接看tryAcquire()方法的實現。

        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) {
                      // (Note: if c != 0 and w == 0 then shared count != 0)
                      //如果state不爲0的同時寫鎖計數爲0,意味着當前有讀鎖,需要返回false
                      //或者當前線程並不是佔有寫鎖的線程返回false
                      if (w == 0 || current != getExclusiveOwnerThread())
                          return false;
                     //如果寫鎖計數大於最大限制,拋出異常
                      if (w + exclusiveCount(acquires) > MAX_COUNT)
                          throw new Error("Maximum lock count exceeded");
                      // Reentrant acquire
                      //寫鎖計數加一 ,不用CAS,因爲寫鎖是獨佔鎖
                      setState(c + acquires);
                      return true;
                  }
      
                  if (writerShouldBlock() ||
                      !compareAndSetState(c, c + acquires))
                      return false;
            //如果CAS設置成功,將當前線程設置爲佔有AQS的線程,返回true 爲什麼不需要CAS設置呢?
          
                  setExclusiveOwnerThread(current);
                  return true;
              }
      

      1.如果state不爲0,那麼就意味着當前有讀鎖或者寫鎖

      1.1如果寫鎖計算爲0,那麼就意味着現在有讀鎖,那麼返回false,進入自旋獲取寫鎖。如果當前線程不是佔有寫鎖的線程,那麼同樣返回false.

      1.2判斷是否寫鎖已經飽和了,飽和了拋出異常。

      1.3 如果前兩步都沒有導致方法提前返回,那麼就開始講寫鎖計數加一,並且返回true,這裏不需要使用CAS設置,因爲寫鎖是獨佔鎖。

      2.如果state爲0,那麼就意味着當前沒有讀寫鎖,但是此時可能有多個嘗試獲取寫鎖的線程執行到這一步,而且這時可能已經有嘗試獲取讀鎖的線程將state改動了,不再爲0了,併發情況下,只要不是原子操作都可能出現問題,所以這個時候我們需要使用CAS來設置,這樣就保證了併發情況下,只有一個線程會被設置爲佔有寫鎖的線程。

    • WriteLock.unlock()

      public void unlock() {
                sync.release(1);
            }
            ```
    

    調用AQS的final方法release()方法,照例直接看tryRelease()方法。

     protected final boolean tryRelease(int releases) {
         //1.是否是當前線程持有寫鎖,不是的話,拋出異常
                if (!isHeldExclusively())
                    throw new IllegalMonitorStateException();
                int nextc = getState() - releases;
         //查看寫鎖計數是否爲0,如果爲0就會返回true,接着就會在CLH中存在阻塞線程的時候去喚醒阻塞節點。
                boolean free = exclusiveCount(nextc) == 0;
                if (free)
                    setExclusiveOwnerThread(null);
                setState(nextc);
                return free;
            }
    

總結

本文只是對讀寫鎖進行一個簡單的分析,這個類很有更多更深入的內容,有興趣的可以閱讀源碼進行深入分析。

參考資料:https://www.jianshu.com/p/6923c126e762

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