搞定ReentrantReadWriteLock 几道小小数学题就够了 (2)

你瞧,使用就是这么简单。但是你知道的,AQS 的核心是锁的实现,即控制同步状态 state 的值,ReentrantReadWriteLock 也是应用AQS的 state 来控制同步状态的,那么问题来了:

一个 int 类型的 state 怎么既控制读的同步状态,又可以控制写的同步状态呢?

显然需要一点设计了

读写状态设计

如果要在一个 int 类型变量上维护多个状态,那肯定就需要拆分了。我们知道 int 类型数据占32位,所以我们就有机会按位切割使用state了。我们将其切割成两部分:

高16位表示读

低16位表示写

搞定ReentrantReadWriteLock 几道小小数学题就够了

所以,要想准确的计算读/写各自的状态值,肯定就要应用位运算了,下面代码是 JDK1.8,ReentrantReadWriteLock 自定义同步器 Sync 的位操作

abstract static class Sync extends AbstractQueuedSynchronizer { 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; } }

乍一看真是有些复杂的可怕,别慌,咱们通过几道数学题就可以搞定整个位运算过程

搞定ReentrantReadWriteLock 几道小小数学题就够了

整个 ReentrantReadWriteLock 中 读/写状态的计算就是反复应用这几道数学题,所以,在阅读下面内容之前,希望你搞懂这简单的运算

基础铺垫足够了,我们进入源码分析吧

源码分析 写锁分析

由于写锁是排他的,所以肯定是要重写 AQS 中 tryAcquire 方法

protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); // 获取 state 整体的值 int c = getState(); // 获取写状态的值 int w = exclusiveCount(c); if (c != 0) { // w=0: 根据推理二,整体状态不等于零,写状态等于零,所以,读状态大于0,即存在读锁 // 或者当前线程不是已获取写锁的线程 // 二者之一条件成真,则获取写状态失败 if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // 根据推理一第 1 条,更新写状态值 setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }

上述代码 第 19 行 writerShouldBlock 也并没有什么神秘的,只不过是公平/非公平获取锁方式的判断(是否有前驱节点来判断)

搞定ReentrantReadWriteLock 几道小小数学题就够了

你瞧,写锁获取方式就是这么简单

读锁分析

由于读锁是共享式的,所以肯定是要重写 AQS 中 tryAcquireShared 方法

protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); // 写状态不等于0,并且锁的持有者不是当前线程,根据约定 3,则获取读锁失败 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; // 获取读状态值 int r = sharedCount(c); // 这个地方有点不一样,我们单独说明 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 != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } // 如果获取读锁失败则进入自旋获取 return fullTryAcquireShared(current); }

readerShouldBlock 和 writerShouldBlock 在公平锁的实现上都是判断是否有前驱节点,但是在非公平锁的实现上,前者是这样的:

final boolean readerShouldBlock() { return apparentlyFirstQueuedIsExclusive(); } final boolean apparentlyFirstQueuedIsExclusive() { Node h, s; return (h = head) != null && // 等待队列头节点的下一个节点 (s = h.next) != null && // 如果是排他式的节点 !s.isShared() && s.thread != null; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppfdf.html