在前文https://blog.csdn.net/weixin_43696529/article/details/104129483已经对Lock、Condition以及AQS相关进行了介绍,接下来就要讲一讲Lock接口的几个实现类:ReentrantLock
、ReentrantReadWriteLock
及其内部类ReadLock
和WriteLock
。本文中带△△的标注代表前文已讲过,不详述。
首先了解下锁的可重入的概念:
一、锁的可重入
“重入“是指线程在获取到锁之后能够再次获取该锁而不会被阻塞,
因此:
1)线程再次获取锁时,需要去识别获取锁的线程是否为当前持有锁的线程,如果是,则再次成功获取。
2)线程获取了几次锁,就需要释放几次锁,只有全部释放完毕,其他线程才能获得锁,因此需要对锁的获取进行自增计算,自增次数表示
当前锁被重复获取的次数,而锁被释放时,每次释放该计数都自减1直到减到0时表示成功释放锁。
二、ReentrantLock
2.1 类图
从上图可以看出:
1.ReentrantLock
实现了Lock
接口
2.内部类Sync
继承了AQS
3. FairSync
和NocfairSync
继承了Sync
另外,ReentrantLock是可重入的独占锁,因此一次只能有一个线程可获取锁,而其他线程会被阻塞在AQS队列,针对获取锁,其提供了公平和非公平的实现,而公平模式和非公平模式主要通过ReentrantLock
的构造方法确定的,如下:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
默认构造函数中,实现了非公平的实现,而带参的构造函数,参数fair为true
时为公平实现,为false
时为非公平实现。
ReentrantLock
的成员变量只有一个Sync对象:
private final Sync sync;
其成员函数除了实现Lock接口提供的几个方法外(如下),其他方法都是很简单,如获取等待队列长度、获取等待的线程等待。
(这几个方法的介绍在上一篇文章有介绍)而对这几个方法的具体实现,都是根据Sync实现了公平锁还是非公平锁。
下面先讲非公平锁的相关方法:
2.2 非公平模式
以下是非公平锁的lock()
方法:
2.2.1 lock()
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
该方法中:
1.首先调用compareAndSetState(0, 1)
尝试将状态值从0改为1,如果CAS成功,表示锁已经被当前线程占用,则将当前线程设为有独占访问权的线程
2.如果CAS失败,表示锁已经被其他线程占用,则调用acquire(1)方法尝试获取锁。
△△acquire(1)
为AQS中的方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
其中tryAcquire()
为尝试获取锁的模板方法,这里的具体实现就是调用了NonfairSync
下的非公平实现方法:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//当前当前同步状态
int c = getState();
//如果状态为0,表示当前没有线程持有锁
if (c == 0) {
//重复lock()中的第一个if语句
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果状态不为0,代表当前有线程持有锁
// 则判断当前持有锁的线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
// 如果是当前线程持有锁
// 1.让当前状态加上acquires(可重入就在这里体现,进行自增)
int nextc = c + acquires;
//2. 如果加上acquires后的状态值仍小于0
if (nextc < 0)
//说明溢出了,抛出异常
throw new Error("Maximum lock count exceeded");
// 如果大于0,则更新当前状态
setState(nextc);
//返回true
return true;
}
// 如果不是当前线程,则返回false,尝试获取失败
return false;
}
简单总结一下流程:
1.首先判断当前状态值state
是否等于0,如果等于0则说明没有线程持有锁,进入2,否则进入3
2. CAS更新状态值,并将当前线程设为独占的
3.判断当前持有锁的线程是否为当前线程,如果是则自增状态值,否则返回false;自增状态时需检查自增后的状态值,如果大于0,则更新state
,如果小于0,说明溢出,抛出异常。
而如果获取失败了则会执行AQS的acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
方法,将当前线程构造为一个独占模式的节点加入同步队列,如果前驱节点为首节点,才可以尝试获取锁,否则跳过处于CANCEL状态的节点,并将前驱节点更新为SIGNAL阻塞自身等待唤醒。
以上就是lock()
的非公平实现,了解了其可重入的实现核心,如果我们想实现一个自己的不可重入的独占锁,就很简单了。
主要就是修改tryAcquire(int arg)
方法,如果CAS成功,则将当前线程设为独占的,否则直接返回false,不进行持有锁是否为当前线程的判断等相关操作。
/*获得锁*/
@Override
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
2.2.2 unlock()
ReentrantLock
的unlock()
也是直接调用了AQS的△△release()
方法:
(ReentrantLock)public void unlock() {
sync.release(1);
}
(AQS)public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
该方法的tryRelease(arg)
也是模板方法,在ReentrantLock
的实现如下:
(非公平和公平模式只是针对lock过程,release的实现是相同的)
protected final boolean tryRelease(int releases) {
// 计算当前状态减去releases后的值
int c = getState() - releases;
// 如果当前线程不是独占模式的,则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 可以理解为是否彻底释放
boolean free = false;
// 如果c为0,说明当前线程已经将锁释放完毕,即重入的次数都已释放
if (c == 0) {
// 置为true,表示已彻底释放
free = true;
// 将当前独占线程清空
setExclusiveOwnerThread(null);
}
// 如果自减一后的state不为0,说明尚未释放完毕,返回false
setState(c);
return free;
}
流程简单的将就是:如果当前状态自减1后为0,则说明锁释放完毕,情况独占锁;如果不为0,则说明重入锁未释放完毕,仍持有资源
如果返回false,就会继续执行release()
的△△unparkSuccessor(h);
方法唤醒后驱节点。而后驱节点被唤醒后就会再次执行acquireQueued()
final boolean acquireQueued(final Node node, int arg) {
..............
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
.............
}
}
此时p==head成立,将当前节点设为head,与原head断开,并返回中断标志,此时如没有被中断,就直接从acquire()
方法返回,否则中断当前线程。
2.3 公平模式
lock()
方法:
该lock方法也是实现了AQS的acquire()方法,但是是在FairSync
实现了公平模式获取锁:
protected final boolean tryAcquire(int acquires) {
//当前线程
final Thread current = Thread.currentThread();
//当前同步状态
int c = getState();
//c等于0,说明没有线程占有锁
if (c == 0) {
if (!hasQueuedPredecessors() &&//判断有没有其他线程在排队,因为是公平模式,如果有排队,则应该按排队顺序唤醒
compareAndSetState(0, acquires)) {//如果没有线程排队,则尝试CAS更新同步状态
setExclusiveOwnerThread(current);//如果CAS更新成功,则将当前线程设为独占
return true;
}
}
//否则已经有线程获得锁,就要判断是否是自己,如果是则可以重新获取锁(重入)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
从源码可以看出,只是在当前状态值为0时,即没有线程持有锁时增加了一个hasQueuedPredecessors()
判断,源码如下:
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t && //1.头尾节点不相同,说明队列非空,如果队列空,则返回false,说明没有线程在等待
((s = h.next) == null || s.thread != Thread.currentThread());//2. 队列非空,如果等待线程不是当前线程,则返回true,如果是当前线程,则返回true
}
该方法返回当前等待队列是否有其他线程等待,true
表示有其他线程在等待,false
表示没有
如果返回false
,则继续进行后面的步骤,同非公平步骤相同
可见,公平锁会考虑线程等待的优先顺序,而非公平锁不考虑顺序,谁来谁就去争夺锁。
锁的释放以及lock的剩余实现见前文AQS部分的讲解:显示锁Lock、Condition接口、LockSupport以及AQS(同步队列、条件等待队列)详解
2.4 为什么ReentrantLock默认使用非公平实现?
- 非公平实现比公平实现性能高,它不需要检查是否有线程排队。减少了线程切换带来的开销
- 非公平实现先进行CAS,后进行acquire,而公平直接进行了acquire
这样显然会出现一个问题:
就是非公平实现可能导致某些线程一直获取不到锁,出现“线程饿死”;
三、读写锁ReentrantReadWriteLock
在读多写的场景下,该锁就非常适用。该类维护了一个读锁ReadLock
和一个写锁WriteLock
。
关于写锁:
1.写锁是一个支持可重入的排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取(读状态不为 0)或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。
2.如果读锁被获取,则写锁不能被获取。
因为读写锁要保证写锁的操作对读锁可见,如果读锁已被获取,又对写锁获取,那么其他读线程就无法感知到当前写线程的操作。因此,写锁被当前线程获取的前提是其他读线程全部释放读锁,
3.写锁一旦被获取,则其他读写线程的访问均被阻塞。
关于读锁:
1.读锁是一个支持重进入的共享锁,它能够被多个线程同时获取。
2.在写锁没有被获取时,读锁可以被多次获取。获取成功则安全地增加读状态(该状态是所有线程获取读锁的次数和,但每个线程获取读锁的次数保存在ThreadLocal
中)
3.如果在获取读锁时,写锁被其他线程获取,则当前线程阻塞等待
关于锁的升降级:
1.锁降级指的是写锁降级为读锁
:当前持有写锁,同时再去获取读锁,最后再释放写锁
2.锁升级指的是读锁升级为写锁:当前持有读锁,获取写锁,最后释放读锁,但RentrantReadWriteLock
不支持锁的升级,因为如果多个线程持有读锁,任意一个线程升级到了写锁,这样就不能保证写线程的更新对其他读线程的可见性。
3.1 类结构
从图中可以见出:
1.ReentrantReadWriteLock
实现了ReadWriteLock
接口,在该接口中提供了一个读锁和一个写锁,其具体实现就ReentrantReadWriteLock
中的读锁和写锁。
2.ReentrantReadWriteLock
有5个内部类,分别为ReadLock
、WriteLock
、
FairSync
、NonfairSync
和Sync
,其中FairSync
和NonfairSync
是Sync
的公平和非公平的实现,Sync实现了AQS
,读锁和写锁实现了Lock
接口。
3.在Sync下又有两个内部类:ThreadLocalHoldCounter
和HoldCounter
3.2 Sync
首先看Sync类:
它的两个内部类源码如下:
static final class HoldCounter {
int count = 0;
final long tid = getThreadId(Thread.currentThread());
}
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
其中HoldCounter
内部维护了一个count变量和一个tid,其中count
表示一个读线程重入的次数,tid表示持有读锁的当前线程的id,唯一标识一个线程。
而ThreadLocalHoldCounter
继承了ThreadLocal
,并且将HoldCounter
作为泛型,重写了ThreadLocal的initialValue()
方法,可直接通过get获取当前线程的count值。
在看起类属性前,先了解其读写状态的设计:
读写状态的设计:
ReentrantLock 中,同步状态表示锁被一个线程重
复获取的次数,而读写锁的自定义同步器需要在同步状态(整型变量)上维护多个读线程和一个写线程的状态,因此就需要对该遍历进行“按位切割使用”。
同步状态的高 16 位表示读(读锁线程数),低 16 位表示写(写锁重入此数)
读写锁通过位运算确定当前状态。
假设当前同步状态值为 S,写状态等于 S&0x0000FFFF
(将高 16 位全部抹去),读状态等于 S>>>16(无符号补 0 右移 16 位)。当写
状态增加1时,等于S+1,当读状态增加1时,等于S+(1<<16),也就是S+0x00010000。
根据状态的划分能得出一个推论:S 不等于 0 时,当写状态(S&0x0000FFFF)等
于 0 时,则读状态(S>>>16)大于 0,即读锁已被获取。
(图片来源于网络)
Sync
的成员属性如下:
读写锁共用同步状态,高16用于读,低16位用于写
static final int SHARED_SHIFT = 16;
// 读锁的单位(因为读锁是高16位,因此每次要加上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;
//读锁的计数,当前同步状态右移16位获取读锁计数
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//获取写线程的数量(获取写锁的重入次数)
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
// 保存本地线程获取读锁的重入次数,当前线程的计数变为0时删除之
private transient ThreadLocalHoldCounter readHolds;
// 表示最后一个成功获取读锁的线程的计数的缓存
/*
获取和释放读锁的时候,需要更新HoldCount,会先检测缓存的是否为空,如果不为空,则判断线程ID是否和当前线程ID相同
如果相同,就直接通过缓存更新HoldCount
否则,从readHolds中获取HoldCounter对象,赋值给该缓存,最后再更新HoldCounter的计数(不理解的话需要先看下面的源码分析,回头再看就理解了)
*/
private transient HoldCounter cachedHoldCounter;
//第一个获得读取锁的线程。(这里使用此变量应该就是为了提高效率,当只有一个线程获取读锁时,可直接从此变量获取,不需要操作readHolds.get()了)
private transient Thread firstReader = null;
//是firstReader的重入数。
private transient int firstReaderHoldCount;
Sync
的构造函数:
Sync() {
// 初始化本地线程计数器
readHolds = new ThreadLocalHoldCounter();
//确保readHolds的可见性
setState(getState());
}
Sync是公平的还是非公平的是在ReentrantReadWriteLock
的构造中确定的:
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
fair为true
时实现公平模式,为false
时实现非公平模式,同时初始化读锁和写锁。
写锁:
写锁内部就维护了一个Sync,其具体实现由ReentrantReadWriteLock
中的sync决定:
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
}
写锁的lock():
lock()也是调用了AQS的lock()方法,具体实现为Sync下的tryAcquire()
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//如果获取锁失败,则加入同步队列等待,这里参考前文介绍AQS时的讲解,写锁也是独占锁
selfInterrupt();
}
```
```java
protected final boolean tryAcquire(int acquires) {
//获取当前线程
Thread current = Thread.currentThread();
//获取同步状态
int c = getState();
// 获取写线程数量
int w = exclusiveCount(c);
//状态不为0,表示已有其他线程获取读锁或写锁
if (c != 0) {
//a.而如果此时w==0即写锁没有被占用,说明读锁被占用,因此直接返回false,获取失败
if (w == 0 ||
//b.如果当前写锁被持有,则判断持有锁的线程是否为当前线程,如果不是则返回false,如果是则继续
current != getExclusiveOwnerThread())
return false;
//如果持有写锁的线程为当前线程
// 则重新计算当前写锁重入次数后是否大于最大重入次数(2^16-1)
if (w + exclusiveCount(acquires) > MAX_COUNT)
//如果超出最大重入次数,则抛出异常
throw new Error("Maximum lock count exceeded");
//否则更新同步状态,即自增重入次数
setState(c + acquires);
return true;
}
//如果状态为0,表示没有线程获取读写锁,就CAS更新同步状态
if (writerShouldBlock() || //是否应该阻塞当前获取写锁的线程
!compareAndSetState(c, c + acquires))//如果不需要阻塞,则CAS更新同步状态
return false;
//CAS成功,将当前线程设为独占的
setExclusiveOwnerThread(current);
return true;
}
writerShouldBlock()
的公平实现需要调用hasQueuedPredecessors
判断有没有已经在等待获取写锁的线程,如果有则阻塞当前线程,如果没有则返回true;而非公平实现直接返回false
static final class NonfairSync extends Sync {
final boolean writerShouldBlock() {
return false;
}
}
static final class FairSync extends Sync {
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
}
流程总结:
获取当前同步状态state,和写锁重入次数w
- 如果state不为0.说明读锁或写锁被占用
1.1 如果w为0说明写锁被持有,因此读锁已经被持有,直接返回false
1.2 如果w不为0,说明当前写锁被持有,则判断当前写锁线程是否为当前线程,如果是则判断重入次数是否溢出,如果没溢出则更新同步状态返回true;如果不是则直接返回false - 如果为0,说明读写锁都没有被占用,则根据sync是公平还是非公平实现判断当前线程是否需要阻塞,如果不需要阻塞则CAS更新同步状态,成功后将当前线程设为独占的,并返回true,获取写锁成功
写锁的unlock():
同样调用AQS的unlock,tryRelease的实现如下:
public final boolean release(int arg) {
if (tryRelease(arg)) {//释放成功后
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//唤醒下一个节点
return true;
}
return false;
}
```
```java
protected final boolean tryRelease(int releases) {
//1.如果当前线程不是持有写锁的线程,直接抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//2.计算当前状态减去releases后的值
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
//如果重入次数为0,则直接清空锁的持有者。因此重入锁必须将重入次数全部释放后才可真正释放锁,返回true
setExclusiveOwnerThread(null);
//否则更新重入次数,返回false
setState(nextc);
return free;
流程总结:
- 如果当前线程不是持有写锁的线程直接抛出异常
- 如果获取写锁数量为0表示成功释放,置独占线程为空,返回true
- 如果不为0,则更新同步状态,返回false
- 如果成功释放,则唤醒下一个等待的节点
读锁:
结构同写锁相同
读锁的lock():
对应Sync
的tryAcquireShared
:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
//获取当前线程
Thread current = Thread.currentThread();
// 获取当前状态
int c = getState();
// 如果当前写锁被持有,且不是当前线程,返回-1,小于0表示获取失败
// 而如果写锁被持有,且是当前线程持有,说明正在进行锁降级
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 获取当前获取读锁的线程数量
int r = sharedCount(c);
if (!readerShouldBlock() && //具体根据公平或非公平原则判断是否需要阻塞当前线程
r < MAX_COUNT && //读锁线程小于最大值
compareAndSetState(c, c + SHARED_UNIT)) {//CAS更新成功
if (r == 0) { //持有读锁线程为0
// 将当前线程设为第一个持有读锁的线程,即将读锁状态从0变为1
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {//当前线程是第一个线程,即该线程重入
//重入数自增
firstReaderHoldCount++;
} else {//当前线程不是第一个读线程
//先查取缓存,获取缓存计数
HoldCounter rh = cachedHoldCounter;
//1.缓存为空
//2.缓存不为空,但对应的线程id非当前正在运行的线程id
if (rh == null || rh.tid != getThreadId(current))
// 从threadLocal中获取计数
cachedHoldCounter = rh = readHolds.get();
//如果计数为0
else if (rh.count == 0)
//则加入到readHolds中
readHolds.set(rh);
//计数自增
rh.count++;
}
//获取成功,返回1
return 1;
}
return fullTryAcquireShared(current);
}
如果当前线程需要阻塞,或读线程超过最大值,或CAS失败,进入fullTryAcquireShared
方法,循环获取读锁
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
//死循环自旋
for (;;) {
//获取当前状态
int c = getState();
if (exclusiveCount(c) != 0) {//有线程持有写锁
if (getExclusiveOwnerThread() != current)
//且不是当前线程,则返回-1
return -1;
} else if (readerShouldBlock()) {//如果需要阻塞
if (firstReader == current) {///当前线程为第一个获取读锁的
// assert firstReaderHoldCount > 0;
} else {//当前线程不是第一个获取读锁的
if (rh == null) { //计数为空,则将缓存赋值给它
rh = cachedHoldCounter;
//缓存为空或运行线程tid不是当前运行的线程
if (rh == null || rh.tid != getThreadId(current)) {
//从threadlocal中获取
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");
//CAS更新状态值
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;
}
}
}
如果tryAcquireShared
失败,则进入doAcquireShared
private void doAcquireShared(int arg) {
//将当前线程构造为一个共享模式节点加入同步队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//当前节点的前驱节点
final Node p = node.predecessor();
//如果前驱是头节点的话,则表示是第一个排队的线程
if (p == head) {
//再次尝试获取读锁
int r = tryAcquireShared(arg);
//如果获取成功
if (r >= 0) {
//将头节点转为下一个节点,并且向后传播唤醒后面连续的节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)//如果被中断了,则中断自己
selfInterrupt();
failed = false;
return;
}
}
//获取失败后,判断是否需要阻塞,如果需要则阻塞自己,等待唤醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
//获取旧头节点
Node h = head; // Record old head for check below
//将当前节点设为头节点
setHead(node);
//旧头节点或新头节点为null或状态为SIGNAL或PROPAGATE时,
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
//获取下个节点
Node s = node.next;
//下个节点非空且是共享模式的,则唤醒下个节点
if (s == null || s.isShared())
doReleaseShared();
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
//头节点状态
int ws = h.waitStatus;
//头节点状态为SIGNAL时,就需要唤醒其后驱等待节点
if (ws == Node.SIGNAL) {
//将当前节点状态设为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//唤醒下个节点
unparkSuccessor(h);
}
else if (ws == 0 &&//如果状态为0,则CAS更新为PROPAGET
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
流程:
- 尝试获取锁,如果已经被写锁占有则获取失败,否则CAS更新同步状态,成功则返回1,如果需要阻塞或CAS失败读线程超过最大值,则再次重新循环获取读锁,如果还是失败,则返回失败。
- 如果获取读锁失败,则将当前节点构造为共享模式节点加入同步队列
- 如果其前驱节点为头节点,则再次尝试获取锁,如果获取成功,则向后传播依此唤醒后驱读节点
- 如果其前驱节点不是头节点,或获取重新锁失败,则阻塞当前线程等待唤醒
- 被唤醒后继续获取锁。
读锁的unlock():
涉及的类
如下:
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//如果释放成功
if (tryReleaseShared(arg)) {
//唤醒下一个节点
doReleaseShared();
return true;
}
return false;
}
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 != getThreadId(current))
//缓存为空或非当前线程则从threadlocal获取
rh = readHolds.get();
//获取当前线程的获取读锁次数
int count = rh.count;
if (count <= 1) {//如果计数小于等于1
readHolds.remove();//彻底释放资源
if (count <= 0)//小于等于-时抛出异常
throw unmatchedUnlockException();
}
// 当前线程的读锁的可重入次数自减
--rh.count;
}
for (;;) {
//获取当前状态
int c = getState();
//释放后的状态
int nextc = c - SHARED_UNIT;
//cas更新状态
if (compareAndSetState(c, nextc))
//如果为0,表示彻底释放,返回ture,否则返回false
return nextc == 0;
}
}
释放流程:
1.如果当前线程是第一个获取读锁的线程,则判断其占有的资源是否为1,即是否只重入一次,如果是,则让第一个读锁的线程置空,否则让第一个读锁的线程的重入次数自减一
2.如果当前线程非第一个读锁线程,则首先获取缓存计数,如果为空或非当前线程,则从threadLocal中获取当前线程的计数,如果计数小于等于1,则移除计数,如果≤0,则抛出异常;最后再让count自减一
3.进入死循环CAS更新同步状态,如果共享锁获取的次数为0,说明完全释放了锁,返回true,进行后面的唤醒操作