Java并发源码分析之ReentrantReadWriteLock

关联文章:
关联文章:
Java并发源码分析之AQS及ReentrantLock
Java并发源码分析之Semaphore
Java并发源码分析之Condition
Java并发源码分析之CyclicBarrier
Java并发源码分析之CountDownLatch

工作原理概要

ReentrantReadWriteLock是基于AQS框架构建的,相关类图如下:

类图

ReentrantReadWriteLock类图
AbstractOwnableSynchronizer:抽象类,定义了独占锁线程,exclusiveOwnerThread
AbstractQueuedSynchronizer:抽象类,继承了AbstractOwnableSynchronizer,里面包含内部类Node
ReentrantReadWriteLock:类,里面包含五个内部类Sync、NonfairSync、FairSync、ReadLock、WriteLock
Sync:类,继承了AbstractQueuedSynchronizer,重写了tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared方法,定义了readerShouldBlock、writerShouldBlock抽象方法
NonfairSync:类,继承了Sync,重写了readerShouldBlock、writerShouldBlock
FairSync:类,继承了Sync,重写了readerShouldBlock、writerShouldBlock
ReadLock:类,继承了Lock,重写了lock、tryLock、unlock
WriteLock:类,继承了Lock,重写了lock、tryLock、unlock

特性

1、不同线程间写写互斥、读写互斥、读读共享
2、同一线程持有写锁,可以再申请写锁,可重入;持有写锁,可以再申请读锁,称之为锁降级;
3、同一线程持有读锁,所有线程都可以再申请读锁,但是所有线程不能再申请写锁(包括当前线程)

原理概述

ReentrantReadWriteLock是基于AQS框架实现的,做到了可重入、可中断,有公平锁和非公平锁的各自实现,也有读锁和写锁的实现。 在使用时,读写锁持有的是一个锁实例,对于写少读锁的场景,可以提高效率。
在ReentrantReadWriteLock中,会将AQS中的同步状态变量state的高16位和低16位进行拆分,高16位记录读锁数量, 低16位记录写锁数量。

Sync

ReentrantReadWriteLock的核心代码都在类Sync中,这里先简单解析一下它

关键常量

 // 共享锁偏移量
 static final int SHARED_SHIFT   = 16;
 // 共享锁的位置, 1左移16位, 即共享锁数量变动单位
 static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
 // 允许申请锁的最大数量
 static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
 // 得到低16位的补码, 这样的int值EXCLUSIVE_MASK的高16位都是0, 低16位都是1
 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

内部类

 // 线程读锁计数器,用于每个线程持有读锁的计数
 static final class HoldCounter {
     // 每个线程持有读锁的数量
     int count = 0;
     // 当前持有读锁的线程id
     // 这里使用线程ID而没有使用引用, 避免垃圾收集器保留引用, 导致无法回收
     final long tid = getThreadId(Thread.currentThread());
 }
 // 通过ThreadLocal维护每个线程的HoldCounter
 static final class ThreadLocalHoldCounter
     extends ThreadLocal<HoldCounter> {
     public HoldCounter initialValue() {
         return new HoldCounter();
     }
 }

关键变量

 // 当前线程持有的可重入读锁的数量, 仅在构造方法和readObject方法中被初始化
 // 当持有锁的数量为0时, 移除此对象
 private transient ThreadLocalHoldCounter readHolds;
 // 成功获取到读锁的最近一个线程的计数器
 private transient HoldCounter cachedHoldCounter;
 // 首个获取读锁的线程
 private transient Thread firstReader = null;
 // 首个获取读锁线程持有读锁的数量
 private transient int firstReaderHoldCount;

读锁/写锁计算方法

 /**
  * 对于AQS的state值, 存储的是一个32位int, ReentrantReadWriteLock将高16位记录读锁数量, 低16位记录写锁数量
  * sharedCount, 传入的c是state, 通过无符号右移16位, 得到的是高16位, 也就是读锁/共享锁的数量
  * exclusiveCount, 传入的c也是state, 通过和EXCLUSIVE_MASK进行位与运算, 得到的是低16位, 也就是写锁/排他锁的数量
  */
 static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

ReadLock获取读锁流程

1、ReadLock–lock获取读锁入口

 // 获取读锁
 public void lock() {
     sync.acquireShared(1);
 }

2、AQS–acquireShared获取共享锁

 public final void acquireShared(int arg) {
 	 // 尝试获取共享锁
     if (tryAcquireShared(arg) < 0)
     	 // 获取失败,将线程加入同步队列
         doAcquireShared(arg);
 }

3、Sync–tryAcquireShared尝试获取共享锁

 protected final int tryAcquireShared(int unused) {
      // 获取当前线程
      Thread current = Thread.currentThread();
      // 获取state
      int c = getState();
      // 如果存在写锁, 并且持有写锁的线程不为当前线程, 返回-1, 获取读锁失败
      if (exclusiveCount(c) != 0 &&
          getExclusiveOwnerThread() != current)
          return -1;
      // 走到这里说明, 不存在写锁, 或者是当前线程持有的写锁
      // 获取读锁数量
      int r = sharedCount(c);
      // readerShouldBlock, 判断当前线程申请读锁是否需要被阻塞, 它在FairSync和NonfairSync中的实现不相同
      // 在FairSync中, 如果同步队列不为空, 且有效的第一个节点不为当前线程, 即队列中有其他线程在等待获取读锁, 则需要阻塞
      // 在NonfairSync中, 如果队列不为空, 且队列中第一个节点的线程在等待获取写锁, 则需要阻塞
      // 如果不需要阻塞, 且读锁数量小于最大读锁数量, 通过CAS将读锁数量+1
      if (!readerShouldBlock() &&
          r < MAX_COUNT &&
          compareAndSetState(c, c + SHARED_UNIT)) {
          // 走到这里表示添加读锁成功
          // r为0, 表示当前线程添加读锁之前, 没有其他线程持有读锁
          if (r == 0) {
              // firstReader赋值为当前线程
              firstReader = current;
              // firstReaderHoldCount赋值为1
              firstReaderHoldCount = 1;
          } else if (firstReader == current) {
              // 如果读锁数量不为0, 并且firstReader为当前线程, firstReaderHoldCount+1
              firstReaderHoldCount++;
          } else {
              // 走到这里说明, 读锁数量不为0, 且首个获取到读锁的线程不为当前线程
              // 获取最近一次获取到读锁的线程计数器
              HoldCounter rh = cachedHoldCounter;
              // 如果计数器为null, 或者计数器的线程id不为当前线程
              if (rh == null || rh.tid != getThreadId(current))
                  // 从ThreadLocal中获取当前线程的计数器
                  // 将最近一次获取到读锁的线程计数器赋值为当前计数器
                  cachedHoldCounter = rh = readHolds.get();
              else if (rh.count == 0)
                  // 走到这里说明, 最近一次获取到读锁的线程是当前线程, 但是count为0, 表示当前线程在获取到读锁之后又释放了, readHolds也被移除了
                  // 重新设置本地线程变量readHolds
                  readHolds.set(rh);
              // 累加计数器中读锁数量
              rh.count++;
          }
          return 1;
      }
      // 走到这里说明, 当前线程需要被阻塞, 或持有读锁的数量超多最大值, 或通过CAS更新读锁数量失败
      // 通过自旋解决获取读锁失败的情况
      return fullTryAcquireShared(current);
  }

4、Sync–readerShouldBlock判断获取读锁是否应该被阻塞

 // 非公平锁实现
 final boolean readerShouldBlock() {
     return apparentlyFirstQueuedIsExclusive();
 }
 final boolean apparentlyFirstQueuedIsExclusive() {
     Node h, s;
     // 同步队列不为空,且队列中的节点为独占节点
     return (h = head) != null &&
         (s = h.next)  != null &&
         !s.isShared()         &&
         s.thread != null;
 }
 // 公平锁实现
 final boolean readerShouldBlock() {
    return hasQueuedPredecessors();
 }
 public final boolean hasQueuedPredecessors() {
    
    Node t = tail; 
    Node h = head;
    Node s;
    // 同步队列不为空,并且第一个有效节点不为当前线程
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

5、Sync–fullTryAcquireShared继续尝试获取读锁

 final int fullTryAcquireShared(Thread current) {
     HoldCounter rh = null;
     // 自旋
     for (;;) {
         // 获取state
         int c = getState();
         // 存在排它锁
         if (exclusiveCount(c) != 0) {
             // 不是当前线程持有的排它锁, 无法申请读锁, 直接返回失败
             if (getExclusiveOwnerThread() != current)
                 return -1;
         // 如果当前线程申请读锁应该被阻塞
         } else if (readerShouldBlock()) {
         	 // 以下操作,官方解释是为了确保我们没有以可重入方式获取读锁
             if (firstReader == current) {
             } else {
                 // 由于是自旋操作, 第一次循环是rh为null, 将rh赋值为最近一次获取到读锁的计数器
                 if (rh == null) {
                     rh = cachedHoldCounter;
                     // 如果rh为null或者计数器的线程id不为当前线程
                     // rh为null,则最近一次获取到读锁的线程释放了读锁 
                     if (rh == null || rh.tid != getThreadId(current)) {
                         // 将rh赋值为当前线程
                         rh = readHolds.get();
                         // 如果当前线程持有的读锁数量为0, 则将当前线程移除
                         if (rh.count == 0)
                             readHolds.remove();
                     }
                 }
                 // 第一次循环之后, rh赋值为了当前线程, 如果当前线程持有的读锁数量为0, 返回获取读锁失败
                 if (rh.count == 0)
                     return -1;
             }
         }
         // 读锁线程等于最大值, 抛出异常
         if (sharedCount(c) == MAX_COUNT)
             throw new Error("Maximum lock count exceeded");
         // 走到这里说明, 分为一下几个情况:
         // 1、存在排它锁, 但是持有排它锁的线程为当前线程
         // 2、不存在排它锁,当前线程应该被阻塞, 但是首个获取到读锁的线程为当前线程
         // 3、不存在排它锁,当前线程应该被阻塞, 首个获取到读锁的线程不是当前线程,但是当前线程持有读锁,现在是可重入申请读锁
         // 3、不存在排它锁, 当前线程不被阻塞, 仅是上次CAS操作未成功
         // 通过CAS获取读锁
         if (compareAndSetState(c, c + SHARED_UNIT)) {
             // 读锁数量为0
             if (sharedCount(c) == 0) {
                 // 设置当前线程为首个获取到读锁的线程
                 firstReader = current;
                 firstReaderHoldCount = 1;
             } else if (firstReader == current) {
                 // 首个获取到读锁的线程是当前线程, firstReaderHoldCount+1
                 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 = rh; // cache for release
             }
             return 1;
         }
     }
 }

通过自旋来申请读锁,先判断是否有排它锁(写锁)的存在,如果存在,判断是否是当前线程,如果为当前线程,可以继续申请读锁。如果不是,直接返回获取读锁失败。
然后判断是否需要阻塞,以公平锁为例,需要阻塞,说明队列中有节点,且第一个有效节点为排他节点。如果当前线程是首个获取到读锁的线程,或者当前持有读锁,则可以继续申请读锁。如果都不是,直接返回获取读锁失败。
如果不需要阻塞,可以继续申请读锁,一直循环申请,直到申请成功,返回成功。或者读锁超过最大数量,抛出异常。

6、AQS–doAcquireShared尝试获取共享锁失败,加入同步队列

 // 共享锁的获取
 private void doAcquireShared(int arg) {
     // 将当前线程封装成共享节点并放入队列的尾部
     final Node node = addWaiter(Node.SHARED);
     boolean failed = true;
     try {
         boolean interrupted = false;
         // 自旋
         for (;;) {
             // 获取node的前驱节点
             final Node p = node.predecessor();
             // 前驱节点为头节点
             if (p == head) {
                 // 尝试获取共享锁
                 int r = tryAcquireShared(arg);
                 if (r >= 0) {
                     // 获取成功, 将前驱节点从等待队列中释放, 同时唤醒前驱节点的后继节点中的线程
                     setHeadAndPropagate(node, r);
                     p.next = null; // help GC
                     if (interrupted)
                         selfInterrupt();
                     failed = false;
                     return;
                 }
             }
             // 当前线程的节点不是头节点, 或者不可以获取读锁
             // shouldParkAfterFailedAcquire, 检查当前节点在获取锁失败后释放需要被阻塞, 如果需要, 执行parkAndCheckInterrupt方法阻塞当前线程
             if (shouldParkAfterFailedAcquire(p, node) &&
                 parkAndCheckInterrupt())
                 interrupted = true;
         }
     } finally {
         if (failed)
             cancelAcquire(node);
     }
 }

将当前线程封装成共享节点加入队列等待,非第一个有效节点,则将线程挂起

ReadLock释放读锁流程

1、ReadLock–unlock释放读锁入口

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

2、AQS–releaseShared释放共享锁

 public final boolean releaseShared(int arg) {
 	 // 尝试释放共享锁,返回释放读锁的线程是否不再持有锁
     if (tryReleaseShared(arg)) {
     	 // 不再持有锁,唤醒同步队列中的节点
         doReleaseShared();
         return true;
     }
     return false;
 }

3、Sync–tryReleaseShared尝试释放共享锁

 protected final boolean tryReleaseShared(int unused) {
    // 获取当前线程
     Thread current = Thread.currentThread();
     // 如果当前线程是首个获取到读锁的线程
     if (firstReader == current) {
         // assert firstReaderHoldCount > 0;
         // 首个获取到读锁的线程的读锁数量为1, 则设置为null
         if (firstReaderHoldCount == 1)
             firstReader = null;
         else
             // 否则-1
             firstReaderHoldCount--;
     } else {
         // 获取最近一个获取到读锁的计数器
         HoldCounter rh = cachedHoldCounter;
         // 如果最近获取到读锁的线程不是当前线程
         if (rh == null || rh.tid != getThreadId(current))
             // rh设置为当前线程的计数器
             rh = readHolds.get();
         int count = rh.count;
         // 如果计数小于等于1, 释放之后不再持有读锁, 则将本地线程变量readHolds删除
         if (count <= 1) {
             readHolds.remove();
             // 如果计数<=0,说明已经不再持有读锁,释放抛出异常
             if (count <= 0)
                 throw unmatchedUnlockException();
         }
         // 计数-1
         --rh.count;
     }
     // 自旋
     for (;;) {
         int c = getState();
         int nextc = c - SHARED_UNIT;
         // 将state的读锁数量-1
         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
             return nextc == 0;
     }
 }

4、AQS–doReleaseShared唤醒同步队列中的节点

 private void doReleaseShared() {
    // 自旋
    for (;;) {
        Node h = head;
        // 同步队列不为空
        if (h != null && h != tail) {
            // 获取头节点的等待状态
            int ws = h.waitStatus;
            // 如果等待状态为等待唤醒
            if (ws == Node.SIGNAL) {
                // 更新等待状态为0
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;  
                // 唤醒头节点的后继节点          
                unparkSuccessor(h);
            }
            // 如果ws=0,说明有线程释放共享锁之后,更新了头节点的等待状态,并且唤醒了后继节点
            // 但是后继节点还未获取到共享锁,又一个线程释放了共享锁
            // 通过CAS将ws更新成更新成Node.PROPAGATE(-3)
            // 如果后继节点获取共享锁失败(即又有两个线程获取到共享锁),还是会将ws更新成Node.SIGNAL(shouldParkAfterFailedAcquire方法里)
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                
        }
        // 如果头节点发生变化,跳出循环
        // 释放锁之后会有其他线程获取到锁,如果是头节点的后继节点,该节点再获取到锁之后会更新头节点
        if (h == head)                   
            break;
    }
}

WriteLock获取写锁流程

1、WriteLock–lock获取写锁入口

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

2、AQS–acquire获取排它锁

 public final void acquire(int arg) {
 	 // 尝试获取排它锁
     if (!tryAcquire(arg) &&
     	 // 尝试获取排它锁失败,则封装为排它节点加入同步队列
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
 }

addWaiter、acquireQueued方法和ReentrantLock中的解析相同,这里不做赘述

3、Sync–tryAcquire尝试获取排它锁

 protected final boolean tryAcquire(int acquires) {
     // 获取当前线程
     Thread current = Thread.currentThread();
     // 获取state的值
     int c = getState();
     // 获取当前写锁的可重入数量
     int w = exclusiveCount(c);
     // 如果state的值不为0, 表示有线程持有锁, 读锁或者写锁
     if (c != 0) {
         // 如果写锁的可重入数量为0, 说明其他线程持有的是读锁, 当前线程无法获取写锁
         // 如果独占锁线程不为当前线程, 当前线程也无法获取写锁
         if (w == 0 || current != getExclusiveOwnerThread())
             return false;
         // 如果写锁可重入数量超过最大数量, 抛异常
         if (w + exclusiveCount(acquires) > MAX_COUNT)
             throw new Error("Maximum lock count exceeded");
         // 走到这里说明是当前线程已经持有写锁, 只需将state加acquires即可, 返回成功
         setState(c + acquires);
         return true;
     }
     // 走到这里说明, state为0, 没有线程持有锁
     // 判断获取写锁线程是否需要阻塞,不需要阻塞则通过CAS将state的值加acquires, 如果设置失败, 返回加写锁失败
     if (writerShouldBlock() ||
         !compareAndSetState(c, c + acquires))
         return false;
     // 走到这里说明加锁成功, 将独占锁线程设置为当前线程
     setExclusiveOwnerThread(current);
     // 返回加锁成功
     return true;
 }

4、Sync–writerShouldBlock判断获取写锁线程是否需要阻塞

 // 非公平锁实现
 final boolean writerShouldBlock() {
 	 // 直接返回false,不需要阻塞
     return false; 
 }
 
 // 公平锁实现
 final boolean writerShouldBlock() {
 	 // 判断同步队列不为空,且第一个有效节点的线程不为当前线程
     return hasQueuedPredecessors();
 }
 public final boolean hasQueuedPredecessors() {
     Node t = tail; 
     Node h = head;
     Node s;
     return h != t &&
         ((s = h.next) == null || s.thread != Thread.currentThread());
 }

WriteLock释放写锁流程

1、WriteLock–unlock释放写锁入口

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

2、AQS–release释放排它锁

  public final boolean release(int arg) {
  	  // 尝试释放排它锁,返回排它锁是否完全释放,即持有排它锁的线程不再持有
      if (tryRelease(arg)) {
      	  // 获取头节点
          Node h = head;
          // 头节点不为空,且头节点的等待状态不为0,唤醒头节点的后继节点
          if (h != null && h.waitStatus != 0)
              unparkSuccessor(h);
          return true;
      }
      return false;
  }

unparkSuccessor之前的博客介绍过,这里不做赘述。

3、Sync–tryRelease尝试释放写锁

  protected final boolean tryRelease(int releases) {
      // 判断持有写锁的线程是否是当前线程, 如果不是, 直接抛出异常, 防止其他线程非法操作
      if (!isHeldExclusively())
          throw new IllegalMonitorStateException();
      // 走到这里说明, 是持有写锁的线程在操作释放锁, 计算释放之后state的值
      int nextc = getState() - releases;
      // 判断写锁的可重复数量释放之后是否为0
      boolean free = exclusiveCount(nextc) == 0;
      // 为0, 则将独占锁线程设置为null
      if (free)
          setExclusiveOwnerThread(null);
      // 将state的值更新
      setState(nextc);
      // 返回写锁是否完全释放
      return free;
  }

ReadLock尝试获取读锁流程

1、ReadLock–tryLock尝试获取读锁入口

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

2、Sync–tryReadLock尝试获取读锁

 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");
         // 通过CAS更新同步状态,更新成功即获取读锁成功
         // 更新失败则再下次循环中继续更新
         if (compareAndSetState(c, c + SHARED_UNIT)) {
         	 // 获取读锁前,读锁为0,将首个获取读锁的线程赋值为当前线程
             if (r == 0) {
                 firstReader = current;
                 // 首个获取读锁的线程持有读锁数量赋值为1
                 firstReaderHoldCount = 1;
             // 如果首个获取读锁的线程为当前线程
             } else if (firstReader == current) {
             	 // firstReaderHoldCount+1
                 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;
         }
     }
 }

WriteLock尝试获取写锁流程

1、WriteLock–tryLock尝试获取写锁入口

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

2、Sync–tryWriteLock尝试获取写锁

 final boolean tryWriteLock() {
    // 获取当前线程
    Thread current = Thread.currentThread();
    // 获取当前state值
    int c = getState();
    // 如果有线程持有锁
    if (c != 0) {
        // 获取写锁的可重入数量
        int w = exclusiveCount(c);
        // 写锁的可重入数量为0, 其他线程持有读锁; 或当前线程非持有写锁的线程, 返回失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 写锁的可重入数量超过最大写锁数量
        if (w == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    // 走到这里说明, 没有线程持有锁, 通过CAS将state+1, 失败则返回加锁失败
    if (!compareAndSetState(c, c + 1))
        return false;
    // 设置独占锁线程为当前线程
    setExclusiveOwnerThread(current);
    // 返回获取成功
    return true;
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值