上一篇文章讲了 ReentrantLock的实现,这片文章继续深入 讲讲ReentrantReadWritrLock的实现
首先回想ReentrantLock中自定义同步器的实现,同步状态(state)表示锁被一个线程重复获取的次数。同样ReentranReadWriteLock也用state来维护维护多个读线程和一个写线程的状态.那么是怎么用一个int变量来维护两种锁的状态呢?答案是 :“按位切割使用”这个变量,读写锁将变量切分成了两个部分,高16位表示读,低16位表示写 。具体如下图:
那么读写锁是如何迅速确定读和写各自的状态呢?
答案:是通过位运算。假设当前同步状态值为S,写状态等于S&0x0000FFFF(将高16位全部抹去),读状态等于S>>>16(无符号补0右移16位)。当写状态增加1时,等于S+1,当读状态增加1时,等于S+(1<<16),也就是
S+0x00010000。
根据状态的划分能得出一个推论:S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读
状态(S>>>16)大于0,即读锁已被获取 。
理解了按位切割来区分 读锁和写锁,那么在去理解ReentranReadWriteLock的加锁和写锁就会容易很多,
首先我们先看看ReentranReadWriteLock写锁的加锁和释放
ReentranReadWriteLock写锁的加锁和释放
这里我们主要看ReentranReadWriteLock重写AQS的tryAcquire方法(如果对这里不理解,可以先看看AQS的实现。它是整个Lock体系的关键。)
看ReentranReadWriteLock写锁的加锁前我们先看看 ReentrantLock公平锁的实现,(为什么要看公平锁呢?因为ReentranReadWriteLock的默认写锁就是公平锁);
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
对比,虽然代码有些不一样但是思路是一样的。都是判断当前锁是否可重入加锁
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
//拿到低16位写锁值
int w = exclusiveCount(c);
//如果有锁
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//如果写锁为0或者 当前线程不是拥有锁的线程(这里是如果有其他线程拥有读锁,是不能加写锁) 返回false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//一直加锁但不释放锁会抛异常 MAX_COUNT是65535
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;
}
这里需要注意:除了重入条件(当前线程为获取了写锁的线程)之外,增加了一个读锁是否存在的
判断 ,如果存在读锁,则写锁不能被获取,原因在于:读写锁要确保写锁的操作对读锁可见,如
果允许读锁在已被获取的情况下对写锁的获取,那么正在运行的其他读线程就无法感知到当
前写线程的操作 。
写锁的释放就和 ReentrantLock锁释放类似。这里就不加赘述!
ReentranReadWriteLock读锁的加锁和释放
获取读锁的实现从Java 5到Java 6之后变得复杂许多,主要原因是新增了一
些功能,例如getReadHoldCount()方法,作用是返回当前线程获取读锁的次数。读状态是所有线
程获取读锁次数的总和,而每个线程各自获取读锁的次数只能选择保存在ThreadLocal中,由
线程自身维护,这使获取读锁的实现变得复杂。
源码如下:
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 != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
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 != getThreadId(current)) {
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");
//这里 SHARED_UNIT 就是1<<<16
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 != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
代码精简化如下:
总得来说和普通的 共享锁差不多,主要就是state状态设置为了 sate + (1<<< 16)
protected final int tryAcquireShared(int unused) {
for (;;) {
int c = getState();
int nextc = c + (1 << 16);
if (nextc < c)
throw new Error("Maximum lock count exceeded");
if (exclusiveCount(c) != 0 && owner != Thread.currentThread())
return -1;
if (compareAndSetState(c, nextc))
return 1;
}
}
读锁的释放也是同共享锁的释放一样 也不再赘述!
ReentranReadWriteLock锁的降级
锁的降级是指 在获取到写锁后降级为读锁。
具体来说就是 获取到写锁后,又获取读锁,再释放写锁,最后释放读锁。
那么为什么不先释放写锁再获取读锁呢?
主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。
如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新。
锁降级的原理:
在读锁获取时 都会判断当前执行的线程是不是拥有锁的线程,如果是就会加读锁成功
代码示例:
public void processData() {
readLock.lock();
if (!update) {
// 必须先释放读锁
readLock.unlock();
// 锁降级从写锁获取到开始
writeLock.lock();
try {
if (!update) {
// 准备数据的流程(略)
update = true;
}r
eadLock.lock();
} finally {
writeLock.unlock();
}
// 锁降级完成,写锁降级为读锁
}try {
// 使用数据的流程(略)
} finally {
readLock.unlock();
}
}
本文主要内容来自《java并发编程艺术》