JUC之读写锁解读
一、什么是读写锁
读写锁实现的功能就是“读写分离”,读可以并发读,写只能串行写,同时,读的时候不能写,写的时候不能读。但是,如何控制读与写,需要我们手动在读代码块上加读锁,写代码上加写锁。
二、读写锁的实现
这里我主要讲一些内部实现原理。
- 读为共享锁
final boolean tryReadLock() {
Thread current = Thread.currentThread();
for (; ; ) {
int c = getState();
//判断是否此时有写锁。
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return false;
//读锁的共享数量
int r = sharedCount(c);
if (r == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//尝试CAS
if (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 true;
}
}
}
- 写为独占锁
final boolean tryWriteLock() {
Thread current = Thread.currentThread();
//获取状态
int c = getState();
//c!=0表示有线程持有锁;
if (c != 0) {
//获取独占锁(写锁)的数量;
int w = exclusiveCount(c);
//w=0,证明写锁为0,当前是读锁状态,写无法获取锁。
//w!=0&当前线程不是独占线程,那么,证明是其它线程获取写锁,不能共用。
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
//正常情况,尝试CAS。
if (!compareAndSetState(c, c + 1))
return false;
setExclusiveOwnerThread(current);
return true;
}
- 锁的计数器
//TODO 写线程持有的重入数
public int getWriteHoldCount() {
return sync.getWriteHoldCount();
}
//TODO 每个读线程持有的重入数
public int getReadHoldCount() {
return sync.getReadHoldCount();
}
- 内部结构
- Syn实现了大部分的操作
- ReadLock、WriteLock封装了Syn中的操作
- FairSyn与UnfairSyn分别有自己的部分实现
三、升降级
1、锁降级
ReentrantReadWriteLock支持锁降级,即从写锁降级变为读锁,但是使用过程仍然要注意:
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public void processCachedData() {
//正常情况下加读锁
rwl.readLock().lock();
//缓存数据无效
if (!cacheValid) {
// Must release read lock before acquiring write lock
//加写锁前必须释放读锁
rwl.readLock().unlock();
//加写锁
rwl.writeLock().lock();
//就在释放读、加写之间,可能数据更新了,必须二次检查
try {
// Recheck state because another thread might have,acquired write lock and changed state before we did.
//第二次检查
if (!cacheValid) {
data = ...
cacheValid = true;
}
// 在释放写锁之前通过获取读锁降级写锁(注意此时还没有释放写锁)
//在写锁没有释放的时候,先申请读锁,成功了再释放写锁,这个过程就称为锁降级。
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // 释放写锁而此时已经持有读锁
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
锁降级的核心:先把持住当前的写锁,再获取读锁,最后释放写锁。如果是先释放读写,再获取写锁,那个过程并不是锁降级。
2、锁升级
ReentrantReadWriteLock是不支持锁升级的。
四、使用场景
- 分布式缓存中间件
- 写的时候需要阻塞
- 读的时候可以并发
- 维护信道channel
- 维护TCP链接的时候可以采用读写锁
使用读写锁的场景最好还是那种读频繁,写稀疏的场景。
来源:https://blog.csdn.net/rekingman/article/details/98886965