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

展开阅读全文

没有更多推荐了,返回首页