ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离, 读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。
同步状态的高16位用来表示读锁被获取的次数 ,低16位为写锁状态。
1 特点
1.1 优点
1 有公平和非公平2个版本
2 锁降级
3 支持重入
4 支持condition
1.2 缺点
1 饥饿
2 不支持锁升级
在同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的。
2 组成
读写锁公用一个 sync对象
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
2.1 主要成员
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
2.2 主要内部类
2.2.1 Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
static final int SHARED_SHIFT = 16;
// 由于读锁用高位部分,所以读锁个数加1,其实是状态值加 2^16
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 写锁的可重入的最大次数、读锁允许的最大数量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 写锁的掩码,用于状态的低16位有效值
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 读锁计数,当前持有读锁的线程数
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 写锁的计数,也就是它的重入次数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}
1 tryAcquire主要作用:用于写锁,控制states,是否阻塞,返回false阻塞,需要进入AQS的CLH队列。
允许锁升级,即先获得了读,可以获得写锁。
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);
//1 持有锁,有可能自己,也可能是其他,可能是读锁,也可能写锁。
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//1.1 写锁未被持有(读锁已经被持有)或其他线程持有写锁,返回失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//1.2 大于最大值,返回异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//1.3 肯定是获的自己写锁,更新states。
setState(c + acquires);
return true;
}
//2 没有线程持有锁(读锁或写锁)
//2.1 应该阻塞(与公平规则有关),返回失败
if (writerShouldBlock() ||
//2.2 CAS 状态失败 ,返回失败
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
2 tryRelease:释放读锁states,返回是否,移除当前节点,需要唤醒CLH队列的下一个节点。
protected final boolean tryRelease(int releases) {
//1 当前排他线程不是当前线程抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//2 计算release后的 states值
int nextc = getState() - releases;
//3 判断当前线程重入次数为0
boolean free = exclusiveCount(nextc) == 0;
//4 如果没有重入了,需要释放锁
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
//4 释放需要释放锁
return free;
}
3 tryAcquireShared
允许锁降级,即先获得了写锁,可以获得读锁,。
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
//1. If write lock held by another thread, fail.
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
//2 不应该被阻塞,读锁次数合法,CAS更新读锁的states+1成功,返回1,不需要阻塞
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//2.1 未有线程持有读锁
if (r == 0) {
//2.1.1 未有线程持有读锁,首节点置为当前线程
firstReader = current;
//2.1.2 未有线程持有读锁,首节点获得次数为1,
firstReaderHoldCount = 1;
}
//2.2 持有读锁线程的头节点为自己
else if (firstReader == current) {
//2.2.2 持有读锁线程的头节点为自己,首节点获得计数次数为1,
firstReaderHoldCount++;
}
//2.3 其他线程是持有读锁线程的头节点
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;
}
//3 如果第二步失败,具有完整重试循环的版本
return fullTryAcquireShared(current);
}
//该代码与
* tryAcquireShared,但总体来说更简单
*使tryAcquireShared与之间的交互复杂化
*重试和懒惰读取保持计数。
//
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
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");
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;
}
}
}
tryReleaseShared
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//1 更新firstReader和firstReaderHoldCount计数
// 更新HoldCounter
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
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;
}
//2 自旋 读锁states-1
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// 释放读锁不影响读线程,如果读锁和写都被释放,将允许写的线程继续执行。
return nextc == 0;
}
}
3 公平性的确保
如何保证公平与非公共锁:writerShouldBlock和readerShoudBlock抽象方法
//公平锁永远返回 是否有前节点
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
//非公平锁 写锁永远返回false,读锁涉及共享,通过检查aqs的CLH队列的头节点是否是排他的。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
4 锁升降级
4.1 锁升级
不允许锁升级,即先获得了读,不可以获得写锁。//1.1部分说明,读锁只要被占用,返回失败。自己获得读锁,也不行。无法升级。
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);
//1 持有锁,有可能自己,也可能是其他,可能是读锁,也可能写锁。
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//1.1 写锁未被持有(读锁已经被持有)或其他线程持有写锁,返回失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
4.2 锁升级
允许锁升级,即先获得了写锁,可以获得读锁。//1. 中,只有另一个线程获得写锁,直接返回失败。自己获得写锁没有影响。
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
//1. If write lock held by another thread, fail.
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
5 读写锁的饥饿问题
ReentrantReadWriteLock非公平模式下,想要获取写锁就变得比较困难了,因为读锁是不互斥的,这个时候大量的读操来读取数据,这个时候就会造成那一条申请写锁的线程会一直被阻塞,这就造成了写线程的饥饿,或者插入其他线程写锁。而无法获得写锁。
6 StampedLock如何解决的饥饿问题
https://blog.csdn.net/chenyixin121738/article/details/109590271
参考
1 https://blog.csdn.net/weixin_43705457/article/details/106211209