ReentrantReadWriteLock是基于AQS实现的读写锁,读锁与读锁不互斥、读锁与写锁互斥、写锁与写锁互斥。
类的继承关系
AQS提供了共享和排它两种模式,acquire/release、acquireShared/releaseShared 是AQS里面的两对模板方法。写锁是排它模式基于acquire/release模板方法实现的,读锁是共享模式基于acquireShared/releaseShared这对模板方法实现的。
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
//读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
//写锁
private final ReentrantReadWriteLock.WriteLock writerLock;
//AQS的实现
final Sync sync;
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new ReentrantReadWriteLock.FairSync() : new ReentrantReadWriteLock.NonfairSync();
readerLock = new ReentrantReadWriteLock.ReadLock(this);
writerLock = new ReentrantReadWriteLock.WriteLock(this);
}
public static class ReadLock implements Lock, java.io.Serializable {
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
}
public static class WriteLock implements Lock, java.io.Serializable {
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
}
}
从表面上看ReentrantReadWriteLock内部定义了readerLock和writerLock两把锁,实际上他们公用一个sync对象,可以理解为一把锁。线程分为两类:读线程和写线程。读线程和读线程之间不互斥,可以同时拿到这把锁。读线程和写线程互斥,写线程和写线程互斥。
从构造函数中可以看出,sync对象和互斥锁ReentrantLock一样有两种实现模式:公平和非公平模式。
abstract static class Sync extends AbstractQueuedSynchronizer {
static final int SHARED_SHIFT = 16;
//左移运算符(<<)将一个数的二进制表示向左移动指定的位数,右侧空出的位置补0。
//SHARED_UNIT的二进制为1 0000 0000 0000 0000 十进制65536
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
//MAX_COUNT二进制为1111 1111 1111 1111 十进制65535
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
//EXCLUSIVE_MASK二进制为1111 1111 1111 1111 十进制65535
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
//无符号右移运算符(>>>)将一个数的二进制表示向右移动指定的位数,
//但左侧空出的位置都填充0,不考虑正负号。
//例如7>>>2 3的二进制是111,向左移动两位得到001 十进制1
//持有读锁的线程重入次数
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//& 按位与运算,两位同时为“1”得1,否则为0,
//EXCLUSIVE_MASK的32位表示为0000 0000 0000 0000 1111 1111 1111 1111,
//所以实际上只有低位的16为会参与计算。
//持有写锁的线程重入次数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}
读写锁也是用state变量来表示锁状态的,只不过是把state变量拆成两半(int占4字节32位),低16位,用来记录写锁的重入次数,高16位,用来记录“读”锁的重入次数。之所以把int拆分成两半而不是用两个int型变量分别表示读锁和写锁的状态是因为无法用一次CAS 同时操作两个int变量。
当state=0时,说明既没有线程持有读锁,也没有线程持有写锁;当state!=0时,要么有线程持有读锁,要么有线程持有写锁。需要进一步通过sharedCount(state)和exclusiveCount(state)判断到底是读线程还是写线程持有了该锁。
WriteLock acquire
public final void acquire(int arg) {
//tryAcquire 尝试去获取锁,如果获取失败,则加入到等待队列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
//独占锁持有的数量
int w = exclusiveCount(c);
if (c != 0) {
//state不等于0,有线程持有锁,需要进一步判断是读线程还是写线程持有锁
//w=0写锁为0,说明当前一定是读线程拿到锁,读写互斥,写线不能去拿锁。
//w!=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");
//重入
setState(c + acquires);
return true;
}
//state=0,此时既没有读线程也没有写线程持有锁,可以去抢锁
//writerShouldBlock 判断写线程在抢锁的时候是否需要排队,有公平锁和非公平锁两种实现
//非公平锁实现,写线程可以直接抢锁,公平锁实现,如果等待队列中有其它线程在排队,则不能去抢锁。
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
WriteLock release
public final boolean release(int arg) {
//尝试释放锁,若释放锁成功,尝试唤醒等待队列头部的线程
if (tryRelease(arg)) {
//释放锁成功,唤醒等待队列中头节点的下一个节点代表的线程
Node h = head;
//h != null && h.waitStatus != 0
//说明头节点的后继节点右线程在等待被唤醒
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//如果持有写锁的不是当前线程,则抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//减少重入次数,如果重入次数为0,直接释放锁
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
ReadLock acquireShared
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
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);
//readerShouldBlock方法判断写线程去获取锁时,是否需要排队。对应公平锁和非公平锁两种实现方式。
//非公平锁实现:读线程在获取锁时,如果队列中第一个元素是写线程,则需要排队阻塞,这样做的目的是防止写线程一直拿不到锁
//公平锁实现:读线程在获取锁时,如果队列中有其他线程在排队,就需要排队阻塞
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
//firstReader 表示第一个读线程
firstReader = current;
//firstReaderHoldCount 第一个读线程重入的次数
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
//如果当前线程不是第一个获取到读锁的线程
//利用ThreadLocal记录该线程重入读锁的次数
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;
}
//如果cas失败,循环获取锁,直到成功
return fullTryAcquireShared(current);
}
ReadLock releaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
//从ThreadLocal中获取计数器
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
//这里使用了自旋锁加cas操作修改state的值
//因为读锁是共享锁,存在多个线程竞争的情况
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;
}
}
总结
1、读写锁也是用int 类型的state变量来表示锁状态的,只不过是把state变量拆成两半(int占4字节32位),低16位,用来记录写锁的重入次数,高16位,用来记录“读”锁的重入次数。
2、读锁和读锁不互斥,读锁和写锁互斥,写锁和写锁互斥。
3、读锁的非公平锁实现:读线程在获取锁时,如果队列中第一个元素是写线程,则需要排队阻塞,这样做的目的是防止写线程一直拿不到锁。读锁的公平锁实现:读线程在获取锁时,如果队列中有其他线程在排队,就需要排队阻塞。
4、写锁非公平锁实现:写线程可以直接抢锁。公平锁实现:如果等待队列中有其它线程在排队,则不能去抢锁。