基本概念
ReadWriteLock (读写锁)内部维护着两个锁,一个用于写操作,即写锁;一个用于读操作,即读锁。
- 写锁,是独占锁,即只能被一个线程持有。
- 读锁,是共享锁,即可以同时被多个线程持有。
它的接口定义如下:
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
ReentrantReadWriteLock(可重入的读写锁) 继承自 ReadWriteLock ,它具有以下特性:
重入:此锁允许 reader 和 writer 按照 ReentrantLock 的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入reader 使用读取锁。writer 可以获取读取锁,但 reader 不能获取写锁。
锁策略:内部的同步器 Sync 实现了公平与非公平同步策略。
锁降级:允许从写锁降级为读锁,实现方式是:先获取写锁,然后获取读锁,最后释放写锁。但是,从读锁升级到写锁是不可能的。
内部构造
1.构造函数
// 构造函数
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = ( fair ) ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
// 获取读锁
public ReentrantReadWriteLock.WriteLock writeLock() {
return writerLock;
}
// 获取写锁
public ReentrantReadWriteLock.ReadLock readLock() {
return readerLock;
}
构造函数中涉及到的几个类都在它内部定义:
// 同步器
static abstract class Sync extends AbstractQueuedSynchronizer {...}
// 非公平同步策略
final static class NonfairSync extends Sync {...}
// 公平同步策略
final static class FairSync extends Sync {...}
// 读锁
public static class ReadLock implements Lock, java.io.Serializable {...}
// 读锁
public static class WriteLock implements Lock, java.io.Serializable {...}
2.锁的重入计数
在 ReentrantReadWriteLock 中由于同时存在写锁、读锁,所以关于它们各自的重入计数是个问题。在这里并没有采用 2 个参数来表示,而是采用一个 32 位的二进制来同时表示各自的重入计数。具体原理如下:
// 这里用[高16位]表示[读锁]计数,[低16位]表示[写锁]计数。
00000000 00000000 00000000 00000000
// 若写锁计数为1,用[低16位]表示就是 1:
00000000 00000000 00000000 00000001
// 若读锁计数为1,用[高16位]表示就是 2^16:
00000000 00000001 00000000 00000000
// 若写锁计数+1 就是 2 = 1 + 1:
00000000 00000000 00000000 00000010
// 若读锁计数+1 就是 2^17 = 2^16 + 2^16
00000000 00000010 00000000 00000000
// 若当前存在写锁、读锁的计数分别为 1,获取写锁过程如下:
00000000 00000001 00000000 00000001
00000000 00000000 11111111 11111111 // 掩码
00000000 00000000 00000000 00000001 // 与操作(&)后的结果
// 若当前存在写锁、读锁的计数分别为 1,获取读锁过程如下:
00000000 00000001 00000000 00000001
00000000 00000000 00000000 00000001 // 无符号左移 16 位(>>>)的结果
明白了读写锁的计数表示以及获取过程,下面来看看其在代码中如何实现:
static final int SHARED_SHIFT = 16;
// 表示是[读锁]计数+1,即 2^16
static final int SHARED_UNIT = ( 1 << SHARED_SHIFT );
// 最大数量,即 2^16-1 = 65535
static final int MAX_COUNT = ( 1 << SHARED_SHIFT ) - 1;
// 掩码,表示 00000000 00000000 11111111 11111111
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;
}
3.读锁计数器
与写锁不同,读锁是共享锁,可以同时被多个线程持有。对于[多个线程]持有读锁的总重入计数可以通过 sharedCount 方法得到,但是对于[单个线程]持有读锁的重入计数如何表示呢?
首先来看 ReentrantReadWriteLock.HoldCounter 类,该类表示写锁的计数器,即每个线程对于读锁的重入计数。
static final class HoldCounter {
// 若同一线程两次调用读锁的 lock 方法,则当前 count = 2
int count;
// 使用 Id 而不是引用是为了避免保留垃圾。注意这是个常量。
final long tid = Thread.currentThread().getId();
}
在 ReentrantReadWriteLock 中无法获得每个线程的读锁计数器,只能获取当前线程的计数器。这里通过 ThreadLocal 实现:
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
// 重写初始化方法
// 关键 -> 调用该类的 get 方法时,可以获取到 HoldCounter
public HoldCounter initialValue() {
return new HoldCounter();
}
}
该类在 Sync 初始化时被创建:
private transient ThreadLocalHoldCounter readHolds;
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState());
}
WriteLock 写锁
WriteLock ,即写锁,它是独占锁。同一时刻只能有一个线程持有它。
1.获取锁
这里 lock 操作为例进行分析:
public void lock() {
// 调用过程:AQS.acquire -> tryAcquire
sync.acquire(1);
}
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
// 取得[锁]的计数(开头介绍过,其包含了读锁和写锁)
int c = getState();
// 取得[写锁]的计数
int w = exclusiveCount(c);
if (c != 0) {
// 关键 -> c != 0 and w == 0 ,说明[读锁]的计数不为 0
// 判断当前线程未持有[写锁],返回 false
if (w == 0 || current != getExclusiveOwnerThread()){
return false;
}
// 判断[写锁]的计数是否超出最大数(65535)
if (w + exclusiveCount(acquires) > MAX_COUNT){
// 抛出异常...
}
setState(c + acquires);
return true;
}
// 代码执行到这:说明读写锁都未被线程持锁(c=0)
if (writerShouldBlock() || !compareAndSetState(c, c + acquires)){
return false;
}
setExclusiveOwnerThread(current);
return true;
}
再来看看 writerShouldBlock 这个方法,它是实现公平写锁与非公平写锁的关键:
// NonfairSync
final boolean writerShouldBlock() {
return false;
}
// FairSync
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
4.释放锁
public void unlock() {
// AQS.release -> tryRelease
sync.release(1);
}
// Sync
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively()){
// 抛出异常...
}
// 判断释放操作后的写锁重入计数
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free){
setExclusiveOwnerThread(null);
}
setState(nextc);
return free;
}
ReadLock 读锁
ReadLock,即读锁。读锁具有以下特性:
由于它是共享锁,可以被多个线程共同持有。
读锁的计数 = 所有持有读锁线程的重入计数。假设读锁被先后线程 t1,t2 持有,t1 的重入计数为 10,t2 的重入计数为 20,则读锁的计数 = 10+20。
若一个线程想要持有读锁需要满足以下条件:
- WriteLock(写锁)没有被其他线程持有。
- 当前线程持有写锁,由于它是独占锁,因此可以直接切换成读锁。
- 读锁的计数没有超出最大值(65535)。
1.获取锁
这里以 lock 为例来分析:
public void lock() {
// 调用过程:AQS.acquireShared -> tryAcquireShared
sync.acquireShared(1);
}
关键来看 tryAcquireShared:
// 在读锁只被一个线程持有的情况下使用
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
// 最近(后)一个成功持有[读锁]的线程计数器。
private transient HoldCounter cachedHoldCounter;
// 当前线程的线程计数器
private transient ThreadLocalHoldCounter readHolds;
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);
if (!readerShouldBlock() && r < MAX_COUNT
&& compareAndSetState(c, c + SHARED_UNIT)) {
// 若当前线程是第一个持有读锁的线程
// 使用 firstReader,避免了查找 readHolds
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId()) {
// 更新 cachedHoldCounter
cachedHoldCounter = rh = readHolds.get();
}else if (rh.count == 0) {
readHolds.set(rh);
}
rh.count++;
}
return 1;
}
// 获取读锁失败,放到循环里重试。
return fullTryAcquireShared(current);
}
- readerShouldBlock
// Fair
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
// Nonfair
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
- fullTryAcquireShared
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
// 进入自旋状态
for (;;) {
int c = getState();
// 判断当前线程是否持有[写锁]
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current) {
return -1;
}
} else if (readerShouldBlock()) { //没有线程持有写锁,且不允许获取读锁
if (firstReader == current) {
} else {
// 第一次循环时,rh 为空
if (rh == null) {
// 判断 rh 是否是最后一个持有读锁的线程
rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId()) {
// 不是的话,从 readHolds 获取
rh = readHolds.get();
if (rh.count == 0){
// 为 0 表示 rh 是 readHolds 刚初始化出来的
// 没有意义,所有要把它移除
readHolds.remove();
}
}
}
if (rh.count == 0){
return -1;
}
}
}
if (sharedCount(c) == MAX_COUNT) {
// 抛出异常...
}
// 通过 cas 给读锁的重入计数+1
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 != current.getId()) {
rh = readHolds.get();
} else if (rh.count == 0) {
readHolds.set(rh);
}
rh.count++;
cachedHoldCounter = rh;
}
return 1;
}
// 获取读锁失败,继续循环
}
}
2.释放锁
public void unlock() {
// 调用过程: AQS.releaseShared -> tryReleaseShared
sync.releaseShared(1);
}
protected final boolean tryReleaseShared(int unused) {
// 当前线程是第一个持有读锁的线程?
Thread current = Thread.currentThread();
if (firstReader == current) {
// 判断重入计数
if (firstReaderHoldCount == 1){
firstReader = null;
}else{
firstReaderHoldCount--;
}
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId()){
rh = readHolds.get();
}
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0){
// 抛出异常
}
}
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc)){
return nextc == 0;
}
}
}