写点什么

Java Core 「13」ReentrantReadWriteLock 再探析

作者:Samson
  • 2022 年 6 月 20 日
  • 本文字数:3318 字

    阅读完需:约 11 分钟

01-ReentrantReadWriteLock.ReadLock#lock

ReentrantReadWriteLock#readLock 来获取读写锁中的读锁,写锁的使用规则为:

  • 若写锁未被其他线程持有,则当前线程可以立即获得读锁;

  • 否则,当前线程被阻塞,直至能够获取到读锁

  • 若当前线程持有写锁,同时请求

ReentrantReadWriteLock.ReadLock#lock → AbstractQueuedSynchronizer#acquireShared

public final void acquireShared(int arg) {    /** 若可用的共享锁数量小于0,则阻塞当前尝试获取共享读锁的线程 */    if (tryAcquireShared(arg) < 0)	  /** 将当前线程封装到 Node 中,并加入到 sync 队列队尾 */        doAcquireShared(arg);}/** 这段代码与分析 ReentantLock 时的 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 效果类似 */
复制代码

→ ReentrantReadWriteLock.Sync#tryAcquireShared

protected final int tryAcquireShared(int unused) {  Thread current = Thread.currentThread();  int c = getState();  /** 如果写锁不空闲,而且持互斥写锁的线程非当前线程,阻塞当前线程 */  if (exclusiveCount(c) != 0 &&      getExclusiveOwnerThread() != current)      return -1;  /** 下面的情况为:a) 持写锁的线程数量为0,或 b) 持写锁的线程数量不为0,但持锁线程为当前线程 */  int r = sharedCount(c);  /** 公平锁和非公平锁在这里的实现不太一样:   * a) 公平锁:sync 中有其他线程正在等待,则返回 true(当前线程不能获共享读锁);   * 否则,返回 false(当前线程可以用 CAS 尝试获取共享读锁)   * b) 非公平锁:sync 中第一个线程等待获取互斥写锁,则返回 true(当前线程不能获得共享读锁);   * 否则,返回 false(当前线程可以用 CAS 尝试获取共享读锁)   */  if (!readerShouldBlock() &&      r < MAX_COUNT &&      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 != LockSupport.getThreadId(current))              cachedHoldCounter = rh = readHolds.get();          else if (rh.count == 0)              readHolds.set(rh);          rh.count++;      }      return 1;  }  /** 完整的尝试获取共享读锁的方法,处理了上述所有情况,以及上述未处理的情况,例如重入 */  return fullTryAcquireShared(current);}
复制代码

02-ReentrantReadWriteLock.ReadLock#unlock

ReentrantReadWriteLock.ReadLock#unlock → AbstractQueuedSynchronizer#releaseShared

public final boolean releaseShared(int arg) {    if (tryReleaseShared(arg)) {	      /** 读锁、写锁都空闲时,尝试唤醒 writer */        doReleaseShared();        return true;    }    return false;}
复制代码

→ ReentrantReadWriteLock.Sync#tryReleaseShared

protected final boolean tryReleaseShared(int unused) {    Thread current = Thread.currentThread();    if (firstReader == current) {        // assert firstReaderHoldCount > 0;        if (firstReaderHoldCount == 1)            firstReader = null;        else            firstReaderHoldCount--;    } else {        HoldCounter rh = cachedHoldCounter;        if (rh == null ||            rh.tid != LockSupport.getThreadId(current))            rh = readHolds.get();        int count = rh.count;        if (count <= 1) {            readHolds.remove();            if (count <= 0)                throw unmatchedUnlockException();        }        --rh.count;    }    /** 主要内容在这里,如果 state == 0,即读锁、写锁都空闲,则唤醒等待的 writer */    for (;;) {        int c = getState();        int nextc = c - SHARED_UNIT;        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.            return nextc == 0;    }}
复制代码

03-ReentrantReadWriteLock.WriteLock#lock

ReentrantReadWriteLock#writeLock 来获取读写锁中的写锁,写锁的使用规则为:

  • 若读锁、写锁均未被其他线程拥有,则当前线程立即获得写锁;

  • 否则,当前线程阻塞。

  • 若当前线程已获得写锁,则可以再次获得写锁(重入)。

  • 若当前线程已获得读锁,则不可获得写锁,阻塞当前线程。

ReentrantReadWriteLock.WriteLock#lock → AbstractQueuedSynchronizer#acquire → ReentrantReadWriteLock.Sync#tryAcquire

protected final boolean tryAcquire(int acquires) {    Thread current = Thread.currentThread();    /** ReentrantReadWriteLock 中,读锁与写锁的持锁线程数量在同一个值 getState() 中      * 低位部分表示持互斥写锁的线程数量     * 高位部分表示持共享读锁的线程数量     */    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())	          /** w == 0,说明有人持有读锁,所以获取写锁失败 */            /** w != 0,说明有人持有读锁,若不是当前线程,则获取写锁失败 */            return false;	      /** 有线程持有写锁,且持锁线程为当前线程,当前线程获锁成功(可重入) */        if (w + exclusiveCount(acquires) > MAX_COUNT)            throw new Error("Maximum lock count exceeded");        // Reentrant acquire        setState(c + acquires);        return true;    }    /** 读锁和写锁都空闲时,公平锁和非公平锁在这里的处理有点不同     * 公平锁,如果 sync 队列中有其他线程等待,则当前线程获取锁失败;     * 否则,只要 CAS 更新 state 成功则获取锁成功;     * 非公平锁,writerShouldBlock() 返回值一直为 false;     * 即不管 sync 队列,只要 CAS 更新 state 成功,则获取锁成功。     */    if (writerShouldBlock() ||        !compareAndSetState(c, c + acquires))        return false;    setExclusiveOwnerThread(current);    return true;}
复制代码

从上面的源码可以看到,出了某些地方需要考虑共享读锁的情况,其他部分与 ReentrantLock 这种互斥锁基本一致。

04-ReentrantReadWriteLock.WriteLock#unlock

写锁的 unlock 与 ReentrantLock 的 unlock 类似。ReentrantReadWriteLock.WriteLock#unlock → AbstractQueuedSynchronizer#release → ReentrantReadWriteLock.Sync#tryRelease

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);    /** 如果持有写锁的线程数量为0,则返回 true,     * AbstractQueuedSynchronizer#release 会唤醒 sync 队列中的 reader     */    return free;}
复制代码



历史文章推荐

Java Core 「12」ReentrantLock 再探析

Java Core 「11」AQS-AbstractQueuedSynchronizer

Java Core 「10」J.U.C 同步工具类 -2

Java Core 「9」J.U.C 同步工具类 -1

Java Core 「8」字节码增强技术

Java Core 「7」各种不同类型的锁

Java Core「6」反射与 SPI 机制

Java Core「5」自定义注解编程

Java Core「4」java.util.concurrent 包简介

发布于: 刚刚阅读数: 2
用户头像

Samson

关注

还未添加个人签名 2019.07.22 加入

还未添加个人简介

评论

发布
暂无评论
Java Core 「13」ReentrantReadWriteLock 再探析_学习笔记_Samson_InfoQ写作社区