ReentrantReadWriteLock适合读多写少的场景。是可重入的读写锁实现类。其中, 写锁是独占的,读锁是共享的。
支持锁降级(持有写锁、获取读锁,最后释放写锁的过程)
读写锁对于同步状态的实现是在一个整形变量上通过“按位切割使用”:将变量切割成两部分,高16位表示读,低16位表示写。
假设当前同步状态值为S,get和set的操作如下:
1、获取写状态:
S&0x0000FFFF:将高16位全部抹去
2、获取读状态:
S>>>16:无符号补0,右移16位
3、写状态加1:
S+1
4、读状态加1:
S+(1<<16)即S + 0x00010000
在代码层面的判断中,同步状态S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取。
protected final boolean tryAcquire(int acquires) {
//当前线程
Thread current = Thread.currentThread();
//获取state状态 存在读锁或者写锁,状态就不为0
int c = getState();
//获取写锁的重入数
int w = exclusiveCount(c);
//当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁
if (c != 0) {
// c!=0 && w==0 表示存在读锁
// 当前存在读锁或者写锁已经被其他写线程获取,则写锁获取失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 超出最大范围 65535
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//同步state状态
setState(c + acquires);
return true;
}
// writerShouldBlock有公平与非公平的实现, 非公平返回false,会尝试通过cas加锁
//c==0 写锁未被任何线程获取,当前线程是否阻塞或者cas尝试获取锁
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//设置写锁为当前线程所有
setExclusiveOwnerThread(current);
return true;
源码分析:
实现共享式同步组件的同步语义需要通过重写AQS的tryAcquireShared方法和tryReleaseShared方法。读锁的获取实现方法为:
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 如果写锁已经被获取并且获取写锁的线程不是当前线程,当前线程获取读锁失败返回-1 判断锁降级
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//计算出读锁的数量
int r = sharedCount(c);
/**
* 读锁是否阻塞 readerShouldBlock()公平与非公平的实现
* r < MAX_COUNT: 持有读锁的线程小于最大数(65535)
* compareAndSetState(c, c + SHARED_UNIT) cas设置获取读锁线程的数量
*/
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);
源码分析: