JUC解析-ReadWriteLock

435 阅读6分钟

ReadWriteLock中定义了读写锁需要实现的接口,具体定义如下:

public interface ReadWriteLock {
    //创建一个读锁
    Lock readLock();
    //创建一个写锁
    Lock writeLock();
}

在JUC中ReentrantReadWriteLock是基于AQS实现的读写锁实现。

适用场景

在ReentrantLock中,线程之间的同步都是互斥的,不管是读操作还是写操作,但是在一些场景中读操作是可以并行进行的,只有写操作才是互斥的,这种情况虽然也可以使用ReentrantLock来解决,但是在性能上也会损失,ReadWriteLock就是用来解决这个问题的。

实现

在ReentrantReadWriteLock中分别定义了读锁和写锁,与ReentrantLock类似,读锁和写锁的功能也是通过Sync实现的,Sync存在公平和非公平两种实现方式,不同的是表示锁状态的state的定义,在ReentrantReadWriteLock中具体定义如下:

static final int SHARED_SHIFT     = 16;
  static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
  static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
  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; }
  
  //线程的id和对应线程获取的读锁的数量
  static final class HoldCounter {
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    final long tid = Thread.currentThread().getId();
  }
  
  //线程变量保存线程和线程中获取的读写的数量
  static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
      return new HoldCounter();
    }
  }
  
  private transient ThreadLocalHoldCounter readHolds;
  //缓存最后一个获取读锁的线程
  private transient HoldCounter cachedHoldCounter;
  //保存第一个获取读锁的线程
  private transient Thread firstReader = null;  
  private transient int firstReaderHoldCount; 

对于32位的state,用高16位表示读锁的占有次数,低16位表示写锁的占有次数,具体实现都是通过位移和位操作实现的。上面定义的几个成员变量主要是为了提高效率。

ReadLock

lock

  public void lock() {
    sync.acquireShared(1);
  }
  
  protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)    //1
      return -1;
    //获得已经占用读锁的线程数量
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {  //2
      if (r == 0) {                                //3
        firstReader = current;
        firstReaderHoldCount = 1;
      } else if (firstReader == current) {
        firstReaderHoldCount++;
      } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != current.getId())
          cachedHoldCounter = rh = readHolds.get();
        else if (rh.count == 0)
          readHolds.set(rh);
          rh.count++;
        }
        return 1;
      }
    return fullTryAcquireShared(current);         //4
  }

说明:

  1. 如果写锁数量不为0,并且占有写锁的线程不是本调用线程,则直接返回-1,否则执行2步骤
  2. 判断申请读锁是否需要堵塞,如果不需要,并且当前读锁的数量小于MAX_COUNT,并且CAS成功,则说明获取读锁成功,执行3步骤,否则执行4步骤,其中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;
    }
  • 在AQS中堵塞队列的头结点不为空,头结点的后继节点不为空并且节点不是共享节点并且节点的线程不为null,则表示存在等待获取写锁的线程,此时本获取读锁需要堵塞,不能直接获取读锁,这里主要是为了保证队列中等待写锁的线程能有机会获取资源,不能造成写锁一直等待。
  1. 如果读写数量为0,则表示本线程是第一个申请读锁的线程,初始化firstReader和firstReaderHoldCount,否则更新cachedHoldCounter和readHolds,获取写锁成功,退出。
  2. 再次判断写锁是否被占有,并且占有的线程是否为本线程,如果为假则直接返回-1, 否则
  • 如果存在等待写锁的线程,在1步骤中已经判断了持有写锁的线程是本调用线程,但是为了保证等待写锁的线程尽快的获取锁资源,这里判断本调用线程读锁的是否已经全部释放,如果释放则本次读锁的竞争直接退出,否则执行接着执行。
  • 如果读锁的数量达到最大值,直接抛出异常。
  • 设置state状态,如果设置成功,则直接配置相关的线程变量。
  final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    for (;;) {
      int c = getState();
      if (exclusiveCount(c) != 0) {
        if (getExclusiveOwnerThread() != current)
            return -1;
      // 前面已经判断过,当前持有写锁的是本次申请读锁的线程
      } else if (readerShouldBlock()) {
          if (firstReader == current) {
          // assert firstReaderHoldCount > 0;
          } else {
            if (rh == null) {
              rh = cachedHoldCounter;
              if (rh == null || rh.tid != current.getId()) {
                rh = readHolds.get();
                if (rh.count == 0)
                  readHolds.remove();
              }
            }
            if (rh.count == 0)
              return -1;
          }
      }
      //读锁数量达到最大数量
      if (sharedCount(c) == MAX_COUNT)
        throw new Error("Maximum lock count exceeded");
      //CAS更新state
      if (compareAndSetState(c, c + SHARED_UNIT)) {
        if (sharedCount(c) == 0) {
          firstReader = current;
          firstReaderHoldCount = 1;
        } else if (firstReader == current) {
          firstReaderHoldCount++;
        } else {
          if (rh == null)
            rh = cachedHoldCounter;
          if (rh == null || rh.tid != current.getId())
            rh = readHolds.get();
          else if (rh.count == 0)
            readHolds.set(rh);
          rh.count++;
          cachedHoldCounter = rh;
        }
        return 1;
      }
    }
  }

获取读锁总结:

  • 如果不存在线程持有写锁,则获取读锁成功。
  • 如果其他线程持有写锁,则获取读锁失败。
  • 如本线程持有写锁,并且不存在等待写锁的其他线程,则获取读锁成功。
  • 如本线程持有写锁,并且存在等待写锁的其他线程,则如果本线程已经持有读锁,则获取读锁成功,如果不能存在读锁,则此次获取读锁失败。

unlock

  //如果读锁全部释放,则返回true
  protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {
      if (firstReaderHoldCount == 1)
        firstReader = null;
      else
        firstReaderHoldCount--;
      } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != current.getId())
          rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
          readHolds.remove();
          if (count <= 0)
            throw unmatchedUnlockException();
        }
        --rh.count;
      }
      for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
          return nextc == 0;
      }
    }

读锁释放只是修改sync中的相关线程变量以及state的状态,逻辑比较简单,直接看代码

WriteLock

lock

  protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
      // (Note: if c != 0 and w == 0 then shared count != 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;
    }
    if (writerShouldBlock() ||
          !compareAndSetState(c, c + acquires))
      return false;
    setExclusiveOwnerThread(current);
    return true;
  }

说明:

  1. 判断是否有线程持有锁,包括读锁和写锁,如果有,则执行步骤2,否则步骤3
  2. 如果写锁为空(此时由于1步骤判断存在锁,则存在持有读锁的线程),或者持有写锁的不是本线程,直接返回失败,如果写锁数量大于MAX_COUNT,返回失败,否则更新state,并且返回true
  3. 如果需要写锁堵塞判断,或者CAS失败直接返回false,否则设置持有写锁的线程为本线程,并且返回true
  4. 通过writerShouldBlock写锁堵塞判断,具体实现:
  • 公平模式:
  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());
  }

如果队列中存在等待的线程,则直接返回true,加入AQS等待队列中,返回false,直接调用setExclusiveOwnerThread设置写锁的持有线程为本线程。

  • 非公平模式
  final boolean writerShouldBlock() {
    return false; // writers can always barge
  }

说明;直接返回false,表示可以直接调用setExclusiveOwnerThread获取写锁。

unlock

  protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
      throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    //如果写锁空闲
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
      setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
  }

说明:

  1. 如果是非互斥锁,抛出异常
  2. 判断锁的数量,如果为0,则设置持有线程为null,否则更新state