目录
ReentrantReadWriteLock
概述
ReentrantReadWriteLock
来自于jdk 1.5
,位于JUC
包的locks
子包。实现了ReadWriteLock
接口- 它本身没有实现
Lock
接口,因为ReentrantReadWriteLock
看起来就像一个容器,内部维护了ReadLock
和WriteLock
两把锁,我们主要是获取这两把锁,然后使用这两把锁来实现同步,因此对于Lock
的实现交给了内部的ReadLock
和WriteLock
去完成 - 除了对公平性和重入性的支持之外,
ReentrantReadWriteLock
还支持锁降级:获取写锁-->
获取读锁-->
释放写锁,写锁能够降级成为读锁 - 它的实现是基于
AQS
同步器框架的
ReentrantReadWriteLock
源码阅读可需参考 AQS
框架的详解:https://blog.csdn.net/weixin_38192427/article/details/117028828
ReentrantReadWriteLock
源码
源码概览
public class ReentrantReadWriteLock implements ReadWriteLock,
java.io.Serializable {
// 读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
// 写锁
private final ReentrantReadWriteLock.WriteLock writerLock;
// AQS 的子类
final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
//位移常量
static final int SHARED_SHIFT = 16;
//读锁状态单位值65536
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
//读锁的最大总个数、写锁的最大重入次数 -> 65535
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
//写锁的掩码,低位16个l
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 返回读锁的总的数量
static int sharedCount(int c) {
//state无符号右移16位,即可获取读锁的数量
return c >>> SHARED_SHIFT;
}
// 返回写锁重入次数
static int exclusiveCount(int c) {
//state & 低16位的1,即可获取写锁重入次数
return c & EXCLUSIVE_MASK;
}
}
// 公平模式实现
static final class FairSync extends Sync { }
// 非公平模式实现
static final class NonfairSync extends Sync { }
// 读锁实现
public static class ReadLock implements Lock, java.io.Serializable {
private final Sync sync;
......
}
// 写锁实现
public static class WriteLock implements Lock, java.io.Serializable {
private final Sync sync;
......
}
}
ReentrantReadWriteLock
中的 state
原来的 AQS
中,具有一个 state
同步状态变量用来表示线程锁的状态,比如ReentrantLock
中 state
的 0
就表示没有获取锁,大于 1
就表示获取到锁以及重入次数。在 ReentrantReadWriteLock
中,由于具有两把锁,需要使用一个 state
来维护两把锁的状态,此时肯定不能仅仅直接使用 state
的值来表示
ReentrantReadWriteLock
的实现方式是“按位切割”:读写锁将state
变量根据二进制切分成了两个部分,使用高16
位表示读锁状态,低16
位表示写锁状态- 当更新读的状态时,只需要更新
state
的高16
位的二进制值,当更新写锁的状态时,只需要更新state
的低16
位的二进制值,这样读写锁的状态更新互不干扰,实现了使用一个state
变量来表示两把锁的状态
例如,某个读写锁的 state
值为 65538
,那么他表示锁状态如下
上面的图中读锁状态为 1
,写锁状态为 2
,ReentrantReadWriteLock
中的锁都是可重入锁
通过位运算读写锁是可以迅速确定读和写各自的状态。假设当前同步状态值为 S
,写状态等于 S&0x0000FFFF
(将高 16
位全部抹去),读状态等于 S>>>16
(无符号右移 16
位)。当写状态增加 1
时,等于 S+1
,当读状态增加 1
时,等于 S+(1<<16)
,也就是 S+0x00010000
构造器
非公平模式(默认)
public ReentrantReadWriteLock() {
// 内部直接调用的另一个构造器,传递 false
this(false);
}
公平模式
public ReentrantReadWriteLock(boolean fair) {
// 根据公平模式创建对应的 AQS 实例
sync = fair ? new FairSync() : new NonfairSync();
// 创建读锁实例
readerLock = new ReadLock(this);
// 创建写锁实例
writerLock = new WriteLock(this);
}
写锁的获取
- 写锁是一个支持重进入的独占锁
- 如果当前线程在获取写锁时,读锁已经被获取(读状态不为
0
)或者写锁被别的线程抢先获取或者当前线程不是已经获取写锁的线程,则当前线程进入等待状态。否则当前线程第一次或者再一次获取写锁,写状态值+1
获取 writeLock
写锁对象
public ReentrantReadWriteLock.WriteLock writeLock() {
return writerLock;
}
lock()
不可中断获取写锁(加锁)
调用类 WriteLock
的 lock()
方法用于获取写锁,如果获取不到,则线程被阻塞(WAITING
),不会响应中断
public void lock() {
// 调用 AQS 的方法
sync.acquire(1);
}
public final void acquire(int arg) {
// tryAcquire 是自己实现的,其他的方法都是 AQS 提供的
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(int acquires)
方法的重写(重点)
/**
1. Sync的方法,尝试获取写锁,同时实现了重入以及公平和非公平的判断
2. * @param acquires 参数,ReentrantReadWriteLock 传递的 1
3. @return true 成功 false 失败
*/
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取当前 state 值
int c = getState();
// 获取写锁重入数量
int w = exclusiveCount(c);
// c 不等于 0,说明有写锁或读锁
if (c != 0) {
/*
* 如果 w == 0 返回 true,说明没有写锁,有读锁。此时 if 判断返回 false,直接结束 tryAcquire 方法
* 如果 current != getExclusiveOwnerThread() 为 true,表示持有写锁的线程不为当前线程,写锁被其他线程获取了,直接结束 tryAcquire 方法,返回 false
* 上面两种情况,都直接返回false,表示获取写锁失败,将会被加入加到 AQS 的同步队列等待
*/
if (w == 0 || current != getExclusiveOwnerThread())
return false;
/*
* 走到这一步说明 w!= 0,即有线程获取写锁,并且当前线程就是获取了写锁的线程
* 此时判断 w 是否超过最大值 65535,超过了那么直接抛出异常
*/
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 走到这一步,说明可重入次数并有超过限制,则增加当前线程的重入次数
// 表示,当前线程持有写锁,现在是重入,所以只需要修改锁的额数量即可
setState(c + acquires);
return true;
}
// 走到这一步,说明 c= 0,表示目前没有线程获取读锁或者写锁,当前线程是第一批获取写锁的线程(可能同时有其他线程尝试获取)
// writerShouldBlock 方法,用于判断实现公平和非公平模式
// 如果 writerShouldBlock 返回 true,表示不可以尝试获取写锁,只有公平模式才可能返回true,直接结束 tryAcquire 方法,返回 false,表示写锁获取失败
// 如果 writerShouldBlock 返回 false,表示可以尝试获取写锁,再执行 CAS 更新state,如果失败,那么也返回 false,表示锁获取失败
if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) {
return false;
}
// 走到这一步,说明获取锁成功,然后设置获取写锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
- 获取当前
state
值c
以及写锁重入数量w
- 如果
c
不等于0
,说明有写锁或读锁,那么
2.1 如果没有写锁,有读锁或持有写锁的线程不为当前线程,写锁被其他线程获取了,直接返回false
,表示获取写锁失败
2.2 否则,表示此时是写锁的重入,判断写锁可重入次数是否超过最大值65535
,如果超过了那么直接抛出异常
2.3 ==如果没有超出重入最大值,那么更新state
的值,返回true
,表示锁重入获取成功 - 否则,
c == 0
,表示目前没有线程获取读锁或者写锁,当前线程是第一批获取写锁的线程(可能同时有其他线程尝试获取) - 调用
writerShouldBlock()
进行公平和非公平模式的判断,如果writerShouldBlock()
返回true
,那么表示不可以尝试获取写锁,如果直接返回false
,表示锁获取失败 - 如果
writerShouldBlock()
返回false
,表示AQS
同步队列没有线程等待;则执行CAS
更新state
,如果失败,那么表示锁被其他线程先一步占有了,也返回false
,表示写锁获取失败 - 如果
CAS
成功,那么设置获取写锁的线程为当前线程,返回true
,表示第一次获取锁成功
writerShouldBlock()
判断公平与非公平的逻辑
writerShouldBlock()
是实现写锁获取的公平或者非公平的关键方法。writerShouldBlock()
实际上是 Sync
中的一个抽象方法,是由它的子类 FairSync
和 NonfairSync
去实现的
FairSync
(公平模式)和NonfairSync
(非公平模式)对它的实现不一样
// NonfairSync 中的实现
final boolean writerShouldBlock() {
return false;
}
// FairSync 中的实现
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
/**
* 位于AQS中的方法,查询是否有任何线程等待获取锁的时间超过当前线程
* * @return 如果有前驱 返回true 否则 返回false
*/
public final boolean hasQueuedPredecessors() {
// 同步队列尾节点
Node t = tail;
// 同步队列头节点
Node h = head;
Node s;
// 如果头结点等于尾节点,则返回false,表示没有线程等待
// 否则,如果头结点的后继s不为null并且s的线程和当前线程相等,则返回false,表示表示当前线程就是等待时间最长的线程
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
- 由于同步队列中头节点是当前获取锁的线程,而新加入的节点是加入到尾部,那么队列中的第二个节点代表的线程就是请求优先级最高的,即等待时间最长的线程
- 如果头节点等于尾节点,表示此时同步队列中没有线程等待;否则,如果头节点的后继
s
不为null
并且s
的线程和当前线程相等,表示当前线程就是等待时间最长的线程。这两种情况都返回false
,表示没有线程比当前线程更早地请求获取锁,那么当前线程可以去获得锁 - 如果该方法返回
true
,则表示有线程比当前线程更早地请求获取锁。那么当前线程将不会执行CAS
操作去获取锁,而是返回false
,保证了线程获取锁的顺序与加入同步队列的顺序一致,很好的保证了公平性,但同时也明显增加了获取锁的成本。为了性能,ReentrantReadWriteLock
的实现就是默认非公平的 - 公平模式只对初次尝试获取锁的线程有效,如果一个线程已经获取了锁,那么重入的时候,根本就不会走到
writerShouldBlock()
方法的逻辑中去
lockInterruptibly()
可中断获取写锁(加锁)
它对中断进行响应,就是当前线程在调用该方法因为没有获取到锁而被挂起时,如果其他线程调用了当前线程的 interrupt()
方法,则当前线程会被唤醒并抛出 InterruptedException
异常,然后返回
public void lockInterruptibly() throws InterruptedException {
// 直接调用 AQS 的方法 acquireInterruptibly
sync.acquireInterruptibly(1);
}
/**
* AQS 中的方法,可中断式获取写锁
*
* @param arg 参数,ReentrantReadWriteLock 中传递 1
* @throws InterruptedException
*/
public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 如果当前线程被中断,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取写锁
if (!tryAcquire(arg))
// 如果没获取到,那么调用AQS 可被中断的方法
doAcquireInterruptibly(arg);
}
tryLock()
尝试获取写锁(加锁)(重点)
/**
* WriteLock中的方法,尝试获取写锁
* @return true 成功 false 失败
*/
public boolean tryLock( ) {
return sync.tryWriteLock();
}
/**
* Sync 中的方法,尝试获取写锁
* @return true 成功 false 失败
*/
final boolean tryWriteLock() {
Thread current = Thread.currentThread();
int c = getState();
// c 不等于 0,说明读锁或者写锁至少有一个已经被某线程获取
if (c != 0) {
// 获取写锁重入数量
int w = exclusiveCount(c);
// 如果 w = 0,说明没有线程获取写锁,那么肯定有线程获取了读锁,当有线程获取了读锁时,写锁不能被获取,直接返回
// 如果 current != getExclusiveOwnerThread() 为 true,表示当前线程不是已经获取写锁的线程,写锁被其他线程获取了,直接返回
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 写锁的重入数量 > 65535 时
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
// 使用 CAS 方法更新 state 的值 c 为 c+1,返回 true 表示成功
if (!compareAndSetState(c, c + 1))
return false;
// 设置获取写锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
- 如果当前没有其他线程持有写锁或者读锁,或者当前线程就是持有写锁的线程,则尝试获取写锁,成功返回
true
;否则返回false
- 该方法不会阻塞当前线程。但是这里没有
writerShouldBlock()
方法的调用,即不会判断公平模式还是非公平模式,那么默认就是非公平模式了。所以无论指定的是公平模式还是非公平模式,当使用写锁的tryLock()
方法时,都是非公平的
写锁的释放 unlock()
// WriteLock 类的 unlock() 方法
public void unlock() {
sync.release(1);
}
// AQS 中的 release 方法
public final boolean release(int arg) {
// tryRelease 方法是 AQS 的子类去重写实现的方法
if (tryRelease(arg)) {
// 获取头结点
Node h = head;
// 如果头结点不为null并且状态不等于0
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease(int releases)
方法的重写
/**
* Sync中的实现,公平模式和非公平模式都是调用的此方法
* * @param releases 参数,ReentrantReadWriteLock 传递的 1
* @return true 成功 false 失败
*/
protected final boolean tryRelease(int releases) {
// 检查如果不是当前线程则抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 获取释放一次之后的 state 值,这里没有考虑高16位,因为获取写锁时读锁状态值肯定是为 0
int nextc = getState() - releases;
// 获取释放之后的写锁状态,判断是否为 0 ,即写锁是否释放完毕,如果是则为 free = true
boolean free = exclusiveCount(nextc) == 0;
// 如果是释放完,即 state 为 0 ,此时设置获取锁的线程为 null
if (free)
setExclusiveOwnerThread(null);
// 简单地更新状态值,因为必须先获取到锁才能释放锁,因此不存在线程安全问题
setState(nextc);
return free;
}
tryRelease()
的实现其实就是将线程持有写锁的数量减1
,即将state
低16
位的值减1
,若减少后写锁的数量为0
,那么表示线程完全释放锁了,设置获取锁的线程为null
,更新state
值,返回true
- 如果返回
false
,说明此次释放锁并没有完全释放。由于执行该方法的线程必然持有锁,故该方法对state
的更新不需要任何CAS
操作
读锁的获取
- 读锁是一个支持重进入的共享锁
- 它能够被多个线程同时获取,在没有其他线程获取写锁时,读锁总会被成功地获取
- 如果当前线程已经获取了读锁,则增加读状态的值,
AQS
的状态值state
的高16
位的二进制值会增加1
,并且记录当前线程获取读锁的重入次数
获取 readlock
读锁对象
public ReentrantReadWriteLock.ReadLock readLock() {
return readerLock;
}
lock()
不可中断获取读锁(加锁)
调用类 ReadLock
的 lock()
方法用于获取读锁,如果获取不到,则线程被阻塞(WAITING
),不会响应中断
public void lock() {
// 调用 AQS 的 acquireShared 方法
sync.acquireShared(1);
}
// AQS 的 acquireShared 方法
public final void acquireShared(int arg) {
// 调用的是 Sync 中的 tryAcquireShared
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
tryAcquireShared(int unused)
方法的重写
/**
* Sync 的方法,尝试获取读锁,同时实现了重入以及公平和非公平的判断
* * @param unused acquires 参数,ReentrantReadWriteLock 传递的 1
* @return 小于 0 表示没有获取到读锁,否则表示获取到了读锁
*/
protected final int tryAcquireShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取当前状态 state
int c = getState();
// 如果写锁状态不为 0,并且持有写锁线程不是当前线程,表明写锁被其他线程占用
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
// 直接返回 -1,表示没有获取到读锁
return -1;
// 走到这一步,可能是写锁状态为 0,即没有线程获取写锁,但是可能有线程持有读锁
// 或者,就是当前线程获取了写锁,这种情况也可以获取读锁。但是需要注意,当一个线程先获取了写锁,然后获取了读锁处理事情完毕后,要记得把读锁和写锁都释放掉,不能只释放写锁
// 获取读锁的总数量
int r = sharedCount(c);
// 先要调用 readerShouldBlock 判断公平模式,并得到返回值
// 如果公平策略没有要求当前线程阻塞,并且读锁总数量 < 65535,则直接尝试 CAS 更新state 增加 SHARED_UNIT,即高 16 位的读锁增加 1
// 如果 CAS 成功,表示当前线程获取到了读锁
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 如果获取读锁总数量为 0,那么表示此前没有线程获取读锁,当前线程在这个时间点是第一个获取到读锁的线程
if (r == 0) {
// firstReader 表示第一个获取到读锁的线程,记录为当前线程
firstReader = current;
// firstReaderHoldCount 表示第一个获取到读锁的线程获取的的读锁次数,此时变成 1
firstReaderHoldCount = 1;
}
// 否则,表示此前已经有线程获取读锁了,如果当前线程就是第一个获取读锁的线程,则该线程获取读锁的次数自增 1
else if (firstReader == current) {
firstReaderHoldCount++;
}
// 否则,表示此前已经有线程获取读锁了,并且当前线程不是第一个获取读锁的线程
// 记录最后一个获取读锁的线程或记录其他线程读锁的可重入数
else {
// cachedHoldCounter 是一个 HoldCounter 类型的变量,用于记录最后一个获取读锁的线程以及它获取的读锁的次数
HoldCounter rh = cachedHoldCounter;
// 如果 rh 为 null,或者 rh 不为 null,但是当前线程不是此前最后一个获取读锁的线程
if (rh == null || rh.tid != getThreadId(current))
// 那么为当前线程设置一个新的 HoldCounter 对象,或者获取当前线程 HoldCounter 对象,存储当前线程以及获取的读锁次数
// 这个readHolds是一个ThreadLocal对象,只在初始化Sync的时候初始化一次,后续获取读锁的线程都可以使用它,用于存放线程自己获取读锁的次数,相当于一个线程本地变量
// 同时设置当前线程就是最后一个获取读锁的线程
cachedHoldCounter = rh = readHolds.get();
// 否则,表示rh不为null并且当前线程是此前最后一个获取读锁的线程
else if (rh.count == 0)
// 更新当前线程在ThreadLocal中的属性值
readHolds.set(rh);
// 最终当前线程获取的读锁数量都自增1
rh.count++;
}
return 1;
}
// 不满足上面if的条件:
//1.readerShouldBlock 返回 true 公平模式下则说明队列中有线程比当前线程等待时间更久
// 非公平模式下说明,线程中等待最久的节点是尝试获取写锁的节点,那么需要读线程等待,为写锁的获取让行
// 2.读锁获取总数量 >= MAX_COUNT,会抛出异常
// 3 CAS 失败,在上面尝试获取锁时,采用了 CAS,因此多个读线程仍然只有一个会成功,
// 以上情况表示获取锁失败,失败的线程会进入fullTryAcquireShared方法进行重试,而不是直接返回
// fullTryAcquireShared的逻辑和上面的逻辑是类似的,会无限循环处理这些情况
return fullTryAcquireShared(current);
}
- 如果写锁重入状态不为
0
,并且持有写锁线程不是当前线程,表明写锁被其他线程占用,则返回-1
,表示没有获取到读锁 - 否则,可能是当前线程获取了写锁,这种情况也可以获取读锁,这就是“锁降级”。也有可能是,没有线程获取到写锁(写锁重入状态为
0
),但是可能有线程持有读锁。继续下一步 - 如果公平策略没有要求当前线程阻塞,并且读锁获取总数量
< MAX_COUNT
,则直接尝试CAS
更新state
,并且如果更新成功,表示获取到读锁,返回1
- 最终如果在前面的判断中都没有返回,那么调用
fullTryAcquireShared
进一步判断
lockInterruptibly()
可中断获取读锁(加锁)
就是当前线程在调用该方法因为没有获取到锁而被挂起时,如果其他线程调用了当前线程的 interrupt()
方法,则当前线程会被唤醒并抛出 InterruptedException
异常,然后返回
public void lockInterruptibly() throws InterruptedException {
// 调用 AQS 的方法 acquireSharedInterruptibly
sync.acquireSharedInterruptibly(1);
}
/**
* AQS中的方法,可中断式获取读锁
*
* @param arg 参数,ReentrantReadWriteLock 中传递 1
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果当前线程被中断,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取读锁
if (tryAcquireShared(arg) < 0)
// 如果没获取到,那么调用AQS 可被中断的方法
doAcquireSharedInterruptibly(arg);
}
tryLock()
尝试获取读锁(加锁)
尝试获取读锁,如果当前没有其他线程持有写锁,则当前线程获取读锁会成功,然后返回 true
。如果当前己经有其他线程持有写锁则该方法直接返回 false
,但当前线程并不会被阻塞。如果当前线程己经持有了该读锁则简单增加 AQS
的状态值高16
位后直接返回 true
// ReadLock 中的方法,尝试获取读锁
public boolean tryLock() {
// 调用 Sync 中的 tryReadLock 方法
return sync.tryReadLock();
}
final boolean tryReadLock() {
Thread current = Thread.currentThread();
// 这里加了一个循环,如果因为 CAS 获取读锁失败的情况,那么可以再次尝试获取读锁
for (; ; ) {
// 获取当前状态 state
int c = getState();
// 如果写锁状态不为 0,并且持有写锁线程不是当前线程,表明写锁被其他线程占用
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
// tryReadLock() 方法结束,返回 false,获取读锁失败
return false;
// 走到这一步,可能是写锁状态为 0,即没有线程获取写锁,但是可能有线程持有读锁
// 或者,就是当前线程获取了写锁,这种情况也可以获取读锁。但是需要注意,当一个线程先获取了写锁,然后获取了读锁处理事情完毕后,要记得把读锁和写锁都释放掉,不能只释放写锁
// 获取读锁的总数量
int r = sharedCount(c);
if (r == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 如果 CAS 成功,表示当前线程获取到了读锁
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
// firstReader 表示第一个获取到读锁的线程,记录为当前线程
firstReader = current;
// firstReaderHoldCount表示第一个获取到读锁的线程获取的的读锁次数,此时变成 1
firstReaderHoldCount = 1;
// 表示此前已经有线程获取读锁了,如果当前线程就是第一个获取读锁的线程,则该线程获取读锁的次数自增 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 true;
}
}
}
读锁的释放
读锁的每次释放(必须是线程安全的,可能有多个读线程同时释放读锁)均减少 state
的读状态,减少的值是(1<<16
),同时减少自身保存的读锁获取次数 1
// ReadLock 中的方法,共享式释放读锁
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
// tryReleaseShared 释放指锁
if (tryReleaseShared(arg)) {
// 释放成功,必定调用doReleaseShared尝试唤醒后继结点
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared(int unused)
释放读锁
tryReleaseShared()
的实现其实就是将该线程持有读锁的数量减 1
,并将 state
高16
位的值减 1
/**
* Sync中的方法
* * @param unused 参数,ReentrantReadWriteLock 中传递 1
* @return 如果释放之后没有任何锁拿俄米返回true,否则返回false
*/
protected final boolean tryReleaseShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
/*这个if else用于释放该线程自己的读锁记录*/
// 如果当前线程就是第一个获取读锁的线程
if (firstReader == current) {
// 如果获取读锁的数量为 1
if (firstReaderHoldCount == 1)
// 那么 firstReader 置空
firstReader = null;
// 否则,读锁数量自减1
else
firstReaderHoldCount--;
}
// 否则,当前线程不是第一个获取读锁的线程
else {
// 获取最后一个获取读锁的线程的计数器
HoldCounter rh = cachedHoldCounter;
// 如果最后一个获取读锁的线程计数器不为 null 或者最后一个获取读锁的线程不是当前线程
if (rh == null || rh.tid != getThreadId(current))
// 那么获取当前线程的自己的读锁线程计数器
rh = readHolds.get();
// 获取当前线程获取的读锁次数
int count = rh.count;
// 如果读锁次数小于等1
if (count <= 1) {
// 那么移除该线程的读锁计数器
readHolds.remove();
// 如果读锁次数小于等于0,那么说明了当前线程多释放了读锁 或者 该线程并没有获取过读锁,此时抛出异常
if (count <= 0)
throw unmatchedUnlockException();
}
// 当前线程获取的读锁次数自减1
--rh.count;
}
// 下面的for循环用于 CAS 更新 state 直到成功
for (; ; ) {
// 获取 state 值
int c = getState();
// 获取下一个 state,减去 SHARED_UNIT(读计数单位)即表示释放一个读锁
int nextc = c - SHARED_UNIT;
// 如果 CAS 成功
if (compareAndSetState(c, nextc))
// 返回下一个 state 是否为 0,如果是那么返回 true,表示读锁写锁都没了,那么可以唤醒随便一个后继节点
// 否则返回 false,表示还存在至少一种锁,那么不会唤醒后继
return nextc == 0;
}
}
- 首先最开始的
if else
中尝试释放自己保存在线程本地的读锁获取记录,再次还会判断该线程是否还有可释放的读锁次数(>0
),如果没有那么抛出异常,这就相当于独占锁了,即需要先获取读锁,才能释放读锁,获取了n
次,最多也就能释放n
次 - 尝试更新
state
,使state
减少一个读计数单位(SHARED_UNIT= 65536
)。由于同时可能有多个读线程更新state
,因此需要使用CAS
,并且在一个死循环之中,直到CAS
成功为止 - 成功之后,如果新的
state
为0
,即此时既没有读锁也没有写锁被获取,那么返回true
,可以尝试唤醒一个同步队列的有效后继。否则返回false
,将不会唤醒后继节点
ReentrantReadWriteLock
总结
写锁的获取规则
- 一个线程获取写锁时,此时有别的线程持有读锁或写锁时,那么此时这个线程获取写锁会失败
- 一个线程获取写锁时,如果此时这个线程是写锁的重入,并可重入次数不超过
MAX_COUNT
时,此时这个写锁的获取会成功 - 一个线程获取写锁时,如果没有任何线程持有读锁或写锁,并且
writerShouldBlock()
返回false
,说明AQS
同步队列没有线程等待,那么此时这个写锁的获取会成功
写锁的释放
就是将线程持有写锁的数量减 1
,即将 state
低 16
位的值减 1
,若减少后写锁的数量为 0
,表示线程写锁被彻底释放了
读锁的获取规则
- 一个线程获取读锁时,如果已经有线程抢占了写锁,并且不是当前线程,那么此时这个线程获取读锁会失败
- 一个线程在持有写锁时,此时这个线程还要去获取读锁时,那么此时这个线程获取读锁也会失败(如果成功,则会造成死锁问题)
死锁问题参考:https://blog.csdn.net/prestigeding/article/details/53286756
死锁问题参考:https://blog.csdn.net/weixin_43767015/article/details/107177607 - 一个线程在持有读锁时,在公平模式没有要求当前线程阻塞,并且该线程读锁获取总数量
< MAX_COUNT
,那么此时获取读锁会成功
读锁的释放
就是将该线程持有读锁的数量减 1
,并将 state
高16
位的值减 1
,若减少后写锁的数量为 0
,表示线程读锁被彻底释放了
ReentrantReadWriteLock
使用场景
适用于读多写少的场景,在此场景中能发挥它的优势。提高了效率的同时,又可以保证线程的安全
参考:https://blog.csdn.net/prestigeding/article/details/53286756
参考:https://blog.csdn.net/weixin_43767015/article/details/107177607