基础
- 读写锁的定义:对于读锁的获取,即使已经有线程获取了读锁,当前线程也可以获取成功;对于写锁的获取,如果已有线程获取了写锁或者其他线程获取了读锁,那么写锁获取失败;读锁写锁是互斥的。
demo
基于非线程安全的HashMap和读写锁实现的线程安全的缓存容器。
public class Cache {
private HashMap map;
private Lock writeLock;
private Lock readLock;
public Cache(){
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
writeLock = lock.writeLock();
readLock = lock.readLock();
}
public Object get(String key){
readLock.lock();
try {
return map.get(key);
}finally {
readLock.unlock();
}
}
public void put(String key, Object value){
writeLock.lock();
try{
map.put(key, value);
}finally {
writeLock.unlock();
}
}
}
读写锁(以ReentrantReadWriteLock为例)实现分析
状态变量设计
显然读写锁需要共用同一个状态变量(使用AQS作为底层实现),所以我们需要把AQS中的state变量分为两部分一部分用于维护读线程的状态信息,另一部分维护写线程的状态信息。
ReentrantReadWriteLock 中将state变量的低16位用于写线程,高16位用于读线程。所以假设当前同步变量状态位S,那么读状态+1时, S = S + (1<<16);当写状态+1时,S = S+1;
推论:当 S !=0 时,且写状态(S&0X0000FFFF)等于0时,则读状态大于0,即读锁已被获取。
写锁的获取与释放
- 何时失败:当有线程获取了读锁(可能是当前线程,因为不能锁升级);或者其他线程已经获得了写锁
- 何时重入:当前线程已经占有写锁。
- 参照英文注释
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);//获取写状态(同步变量的低16位,位运算获取)
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//这里代表有线程获得了读锁(w==0 & c != 0)
//或者有其他线程获得了写锁(current != getExclusiveOwnerThread())
if (w == 0 || current != getExclusiveOwnerThread())
return false;
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;
}
读锁的获取与释放
参照英文注释
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
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);
}
锁降级
定义:把持写锁,再获取到读锁,再释放写锁。
意义:写锁释放之前把持读锁,可以保证数据的可见性,应用场景?
下午书中示例
public void processData() {
readLock.lock();
if (!update) {
// 必须先释放读锁
readLock.unlock();
// 锁降级从写锁获取到开始
writeLock.lock();
try {
if (!update) {
// 准备数据的流程(略)
update = true;
}
readLock.lock();
} finally {
writeLock.unlock();
}
// 锁降级完成,写锁降级为读锁
}
try {
// 使用数据的流程(略)
} finally {
readLock.unlock();
}
}
问题
- 为什么获取读写锁读锁被获取之后,不能获取写锁?
因为要保证写锁的操作对读锁可见,数据可见性。 - 为什么不能锁升级,为什么可以锁降级?
- 为什么需要锁降级?