ReentrantReadWriteLock提供了一个读写锁的实现,并且有着ReentrantLock相似的语义。
简介
非公平策略此模式下,读写锁获取的顺序是不确定的,服从于可重入的限制。不公平锁意味着持续的竞争可能会无限延迟一个或者更多的读线程或者写线程,但比公平锁有更高的吞吐量。
公平策略除非读锁和写锁都没有被获取(意味着没有等待线程),单条线程尝试获取一个公平的写锁就会被阻塞。(注意非阻塞方法ReadLock.tryLock和WriteLock.tryLock不会遵循这个公平策略并且在可能情况下,将忽略等待的线程,获取锁后马上返回)。
可重入性锁允许读线程和写线程重新获取读或者写锁。写线程可以获取读写,但反过来则不允许。在其它应用中,可重入性在写锁在调用或回调到使用读锁执行读操作的方法里变得很有用。如果读线程尝试获取写锁,则该线程永远也不会成功。
锁降级可重入性也允许从写锁下降到读锁,这是通过获取写锁,然后获取读锁,接着释放写锁达到。不过,从读写到写锁的升级是不可能的。
锁获取的中断读锁和写锁都支持在锁获取过程中的中断操作。
Condition支持具体实现
(1)WriteLock获取锁实现 public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
使用者调用方法writeLock获取写锁对象,该函数返回writeLock成员变量,该变量在构造函数里初始化,类WriteLock是一个公有静态内部类,我们看看实现。
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquire(1);
}
public boolean tryLock( ) {
return sync.tryWriteLock();
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
}
我们集中来看看这几个主要方法(为了方便解析,一些次要的方法没有列出来)。获取写锁的时候,调用lock方法,该方法调用内部类Sync的acquire方法,而这个内部类Sync也是AbstractQueuedSynchronizer的子类,acquire方法会调用tryAcquire尝试获取锁,获取失败进入内部FIFO等待队列,直到获取成功。
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
函数首先调用getState获取锁状态,这里要说明一下,这个锁状态并不是和ReentrantLock一样简单的0和大于等于1表示锁的获取情况,而是把这个锁状态int值分成高半位和低半位的两个unsigned shorts值,低半位表示独占锁(写锁)的获取数(包括可重入),高半位表示共享锁(读锁)的获取数。
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; }
SHARED_UNIT表示添加一个读锁获取数时(高半位添加1),锁状态数要真正添加的数字。MAX_COUNT表示读锁和写锁的最大获取数,SHARED_SHIFT和EXCLUSIVE_MASK可以方便通过位移和掩位获取读锁和写锁数,sharedCount和exclusiveCount就是分别利用这两个数字完成了位移获取读锁获取数以及掩位获取写锁获取数。
//NonfairSync
final boolean writerShouldBlock() {
return false;
}
//FairSync
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
两者的实现都很简单,NonfairSync类的writerShouldBlock直接返回false表示写锁可以插队获取锁,FairSync类的实现则是调用基类AQS的hasQueuedPredecessors方法来的判断当前等待队列里是否有前继结点在等待锁,如果有则返回true,表示需要当前写线程需要被阻塞。
final boolean tryWriteLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c != 0) {
int w = exclusiveCount(c);
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
if (!compareAndSetState(c, c + 1))
return false;
setExclusiveOwnerThread(current);
return true;
}
tryWriteLock的实现与tryAcquire大体相同,最大区别就是没有了writerShouldBlock这个公平策略控制的判断。
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;
}
首先是isHeldExclusively判断,函数判断当前线程是否获取独占锁的线程相等,如果返回false,则要抛出异常IllegalMonitorStateException。然后计算出释放后的锁状态数,如果此状态数的独占锁数为0,则会把当前独占锁线程设为null,然后重新设置锁状态数。
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquireShared(1);
}
public boolean tryLock() {
return sync.tryReadLock();
}
public void unlock() {
sync.releaseShared(1);
}
public Condition newCondition() {
throw new UnsupportedOperationException();
}
}
同样,我们这里省略一些次要方法,集中看看获取锁和释放锁的实现。与WriteLock对比,我们注意到newCondition方法抛出异常UnsupportedOperationException,这是读锁是共享的,Condition要求锁必须在独占模式下才能使用。
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
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 != current.getId())
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
首先,我们回忆一下基类AQS要求函数返回负值代表获取读锁失败;返回0表示本次获取读锁成功,但不允许后继读线程获取,也就是不会唤醒等待队列里后继共享结点;返回正值表示本次获取读锁成功,同时允许后继读线程获取读结点,因此会唤醒等待队列里后继共享结点。
private transient ThreadLocalHoldCounter readHolds;
//针对读锁重入的优化
private transient HoldCounter cachedHoldCounter;
//针对无竞争读锁状态下优化
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
第一个readHolds就是主要每条线程的本地线程变量,其中ThreadLocalHoldCounter实现如下:
static final class HoldCounter {
int count = 0;
final long tid = Thread.currentThread().getId();
}
static final class ThreadLocalHoldCounter
extends ThreadLocal {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
ThreadLocal类实现了这样一个功能:每条线程里都保留对同一个ThreadLocal类的引用,但对每条线程的ThreadLocal类进行读写操作都不会影响其它线程,看起来像每条线程都拥有各自的ThreadLocal类一样。
if (r == 0) {
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++;
}
当读锁获取数r为0,意味着这是第一条获取读锁的线程,于是就记录当前线程为firstReader,并且把firstReaderHoldCount=1;然后如果r不为0,但firstReader等于当前线程,则表示第一条获取读锁线程重入,于是把firstReaderHoldCount加上一;如果以上都不是,则表示这是另外一条线程尝试获取读锁,如果cachedHoldCounter为null或者cachedHoldCounter表示的线程id不是和当前线程id相等,则表示cache失效,需要调用readHolds.get()获取当前线程的HoldCounter(如果不存在则会创建一个新的HoldCounter),另外如果cache是当前线程的HoldCounter,但此时count为0,则需要重新把cache设置回去,因为后面的release释放的时候会remove。这样确保cache是当前线程的HoldCounter之后,把当前cahce的count值加一。
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
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");
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; // cache for release
}
return 1;
}
}
}
函数实现看上去有点复杂,但事实上只是tryAcquiredShared的逻辑基础上,增加了循环重试以及延迟的读锁数记录的逻辑。
//NonfairSync
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
//FairSync
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
非公平策略调用来AQS基类的一个方法,apparentlyFirstQueuedIsExclusive,我们来看看实现:
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
此函数对于如果存在第一个在等待队列中的结点,是独占模式结点,则返回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;
}
}
函数实现原理分为两大步骤:
两个步骤里步骤1不需要自循,是因为所做变量更改都是基于ThreadLocal的,并不会影响其它线程,步骤2由于要CAS修改锁状态数,因此需要自循确保成功。
总结
到此,ReentrantReadWriteLock的框架已经解析完成。当然,如果感兴趣的话,还有少部分关于锁状态的辅助函数,可以自行理解。