目录
一、概述
读写锁底层基于AbstractQueuedSynchronizer实现的,他拥有读锁和写锁。多线程可以共享读,但是不能同时读写、写读、写写。读多写少的场景效率较高。
二、问题
2.1 读写锁特点
2.2 什么是锁降级,锁降级有哪些好处,不用锁降级的问题
2.3 是否允许锁升级
2.4 公平和非公平模式如何体现
2.5 如何记录独占锁个数和共享锁个数
2.6 如何记录每个线程重入共享锁次数
三、源码
3.1 Sync属性含义
static final int SHARED_SHIFT = 16;
// 共享模式每次加1,用到了这个常量
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 共享模式下的最大值2^16-1
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 低16位都是1
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/**
* 取int c的高16位,得到获取读锁的次数,包括重入
* @param c state状态
* @return 共享模式下state的值
*/
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/**
* 获取独占模式下的数量,其实是重入的次数
* 获取c的低16位
* @param c state状态
* @return 独占模式下state的值
*/
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
/**
* 读线程重入计数器,被cachedHoldCounter拥有
*/
static final class HoldCounter {
// 读线程重入的次数
int count = 0;
// 唯一标识当前线程,获取当前线程的tid属性的值
final long tid = getThreadId(Thread.currentThread());
}
/**
* 本地线程计数器,每个线程拿到的HoldCounter都不一样,记录每个读线程的重入次数。第一次获取读锁线程除外
*/
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
/**
* 本地线程计数器
*/
private transient ThreadLocalHoldCounter readHolds;
/**
* 缓存的计数器
*/
private transient HoldCounter cachedHoldCounter;
/**
* 第一个获取读锁的线程
*/
private transient Thread firstReader = null;
/**
* 第一个读线程的计数
*/
private transient int firstReaderHoldCount;
Sync() {
readHolds = new ThreadLocalHoldCounter();
// 保证readHolds内存可见 todo ?
setState(getState()); // ensures visibility of readHolds
}
3.2 tryAcquire独占锁尝试获取方法
/**
* 独占锁
* 读锁已占则写锁无法获取
* 写锁被其他线程占用则无法获取
* 可重入
* @param acquires 获取资源数
* @return 是否
*/
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
// 获取独占锁重入次数
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
// (已经重入锁没被获取,但共享锁被获取了) 或者 (独占锁被获取了 并且 当前线程不是获取锁的线程) 直接返回获取失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 假如重入次数超过了最大值抛出异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 这里一定是重入了
setState(c + acquires);
return true;
}
// 读写锁都没获取过进入这里
// 如果发现写锁应该被阻塞 或者 cas设置失败 都直接返回false
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 设置当前线程为独占线程
setExclusiveOwnerThread(current);
return true;
}
3.3 tryRelease独占锁尝试释放
/**
* 尝试释放独占锁
* @param releases 释放数
* @return 是否完全释放
*/
protected final boolean tryRelease(int releases) {
// 如果当前线程没独占锁,则抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 设置下一状态
int nextc = getState() - releases;
// 判断下一状态是否符合全部独占资源释放的条件
boolean free = exclusiveCount(nextc) == 0;
// 如果完全释放锁,则取消独占线程
if (free)
setExclusiveOwnerThread(null);
// 设置状态为下一状态
setState(nextc);
// 返回是否完全释放
return free;
}
3.4 tryAcquireShared 尝试获取共享锁
/**
* 共享锁尝试获取
*/
protected final int tryAcquireShared(int unused) {
// 当前线程
Thread current = Thread.currentThread();
int c = getState();
// 获取独占锁的state,不为0代表独占锁已被占用
if (exclusiveCount(c) != 0 &&
// 不是当前线程拥有的锁
getExclusiveOwnerThread() != current)
// 直接返回负数,尝试获取失败
return -1;
// 获取共享锁的state
int r = sharedCount(c);
// 如果读锁不应该阻塞 并且 共享锁获取次数小于最大值 并且 cas设置状态成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 如果之前共享锁没被获取过
if (r == 0) {
// 记录获取共享锁时状态为0的线程
firstReader = current;
// 初始计数器为1
firstReaderHoldCount = 1;
// 如果当其线程等于firstReader,则将计数器加一
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// 如果其他线程获取了共享锁,获取缓存计数器
HoldCounter rh = cachedHoldCounter;
// 如果缓存计数器没被初始化过 获取 计数器的tid不是当前线程
// 短路设计,提高效率。rh为null说明上一个缓存一定不是当前线程的
if (rh == null || rh.tid != getThreadId(current))
// 从本地线程计数器取出当前线程对应的计数器
cachedHoldCounter = rh = readHolds.get();
// 如果rh!=null 并且 rh是当前线程的计数器 并且 缓存计数器的值为0
else if (rh.count == 0)
// 这步有什么意义,readHolds应该能拿到初始值呀,难道是为了后面的rt.count++
// 我认为这种场景不会出现,因为在tryReleaseShared里遇到count为0的计数器都删掉了
readHolds.set(rh);
// 计数器加一
rh.count++;
}
// 获取成功
return 1;
}
return fullTryAcquireShared(current);
}
3.5 fullTryAcquireShared死循环获取读锁。包含锁降级策略。
/**
* Full version of acquire for reads, that handles CAS misses
* and reentrant reads not dealt with in 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();
// 独占锁获取次数不为0
if (exclusiveCount(c) != 0) {
// 是否是当前线程获取的独占锁,不是则返回获取失败
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
// 如果独占锁获取的次数为0 并且 读锁应该被阻塞
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
// 如果共享锁的获取锁时状态为0的线程是当前线程,则什么都不做
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
// 如果共享锁获取锁时状态为0的线程不是当前线程
// 如果计数器为null
if (rh == null) {
// 计数器等于缓存的计数器
rh = cachedHoldCounter;
// 如果缓存计数器为null 或者 当前缓存计数器 不是当前线程应该持有的
if (rh == null || rh.tid != getThreadId(current)) {
// rh变为当前线程持有的计数器
rh = readHolds.get();
// 如果计数为0
if (rh.count == 0)
// 移除该计数器 todo ?
readHolds.remove();
}
}
// 如果rh的计数为0 则返回-1
if (rh.count == 0)
return -1;
}
}
// 如果共享锁获取次数达到了最大值,抛出异常
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// cas设置共享锁数加一
if (compareAndSetState(c, c + SHARED_UNIT)) {
// 如果原先没获取过共享锁
if (sharedCount(c) == 0) {
// 记录firstReader线程 和 计数器为1
firstReader = current;
firstReaderHoldCount = 1;
// 如果首次获取共享锁的线程是当前线程,则计数器加一
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// rh为null,则将其赋值为缓存计数器
if (rh == null)
rh = cachedHoldCounter;
// 如果缓存计数器为null,并且当前线程不是缓存计数器对应的线程
if (rh == null || rh.tid != getThreadId(current))
// 另rh为当前线程的计数器
rh = readHolds.get();
// 如果计数器值为0
else if (rh.count == 0)
// 则设置值 todo ?
readHolds.set(rh);
// 计数器加一
rh.count++;
// 更新缓存计数器
cachedHoldCounter = rh; // cache for release
}
// 返回成功
return 1;
}
}
}
3.6 tryReleaseShared 尝试释放共享锁
/**
* 释放共享锁
*/
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 如果firstReader是当前线程
if (firstReader == current) {
// 如果计数器为1,设置firstREADER为null
if (firstReaderHoldCount == 1)
firstReader = null;
else
// 否则计数器减一
firstReaderHoldCount--;
} else {
// 得到缓存计数器
HoldCounter rh = cachedHoldCounter;
// 如果上一个缓存计数器不是当前线程的
if (rh == null || rh.tid != getThreadId(current))
// rh变为当前线程的缓存计数器
rh = readHolds.get();
// 获取缓存计数器的值
int count = rh.count;
// 如果缓存计数器的值是1
if (count <= 1) {
// 当前线程不再占有了共享锁
readHolds.remove();
// 如果count是0还有释放,说明不正常
if (count <= 0)
throw unmatchedUnlockException();
}
// 计数器减一
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
3.7 公平锁类
/**
* 公平模式下,读写锁总会先判断是否需要排队
*/
static final class FairSync extends Sync {
/**
* 写线程是否应该阻塞
* 根据线程进入同步等待队列时是否需要排队
* @return 是否
*/
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
/**
* 写线程是否应该阻塞
* 根据线程进入同步等待队列时是否需要排队
* @return 是否
*/
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
3.8 非公平锁类
/**
* 非公平情况下,
* 写锁总是先尝试去获取锁
* 读锁会根据同步队列队首的节点是否为独占的模式判断自己是否应该阻塞
*/
static final class NonfairSync extends Sync {
/**
* 写线程是否应该阻塞
* 写线程应该直接去尝试获取锁
* @return false
*/
final boolean writerShouldBlock() {
return false; // writers can always barge
}
/**
* 判断读线程是否应该被阻塞
* 如果队首是独占模式的节点,则不应该尝试去获取锁
* @return 是否
*/
final boolean readerShouldBlock() {
// 判断同步等待队列的队首是否是独占模式的节点。如果是则应该阻塞
return apparentlyFirstQueuedIsExclusive();
}
}
参考资料
- [1] https://www.cnblogs.com/stateis0/p/9062062.html
- [2] https://www.pdai.tech/md/java/thread/java-thread-x-lock-ReentrantReadWriteLock.html