文章目录
(一)AQS简介
AQS简称AbstractQueuedSynchronizer,是一种实现阻塞锁和依赖FIFO队列的同步器框架。其依赖于一个原子变量state,子类要想使用这个同步器,必须要实现这个AQS,对state(私有可见)变量的操作都需要是原子操作主要是依赖于CAS,同时更改state只能是调用AQS本身提供的原子方法。
AQS主要使用情况:ReentrantLock、ReentrantReadWriteLock、CountdownLatch、Semaphore等。
(二)AQS原理剖析
利用ReentrantLock作为切入点对AQS进行源码分析==>ReentrantLock有一个核心成员变量sync(private final Sync sync),Sync是ReentrantLock类的内部类,其继承于AbstractQueuedSynchronizer。ReentrantLock是依赖于sync变量来调用AQS中的方法和重写的方法。
ReentrantLock源码分析:
源码思想
加锁:
(1)使用构造方法设置该锁为公平锁或非公平锁;(本处选定非公平锁)
(2)调用lock方法进行获取锁,第一次在NonfairSync中使用CAS直接获取(此处默认未获取成功),第一次获取失败后,会执行AQS中的acquire方法;
(3)当执行到AQS中acquire方法时,首先会再次尝试获取锁tryAcquire被子类NonfairSync重写,实际上是执行Sync中的nonfairTryAcquire方法, 再次执行CAS进行锁获取(此处再次默认未获取成功),然后再判断当前锁对象是否被同一线程获取,可重入锁的由来(默认不是);
(4)当tryAcquire方法获取失败后,会再次执行addWaiter方法,以独占模式进行创建等待着包含了哨兵节点的创建和当前线程创建的node节点连接;
(5)当等待者创建完成后,执行acquireQueued入队操作,会判断当前节点的前节点是否为头节点,如果是则再次进行tryAcquire获取锁资源,若不是,则进行信号量设置和真正意义上的线程阻塞LockSupport.park(this)进行阻塞;
说明:每个等待者(线程)的初始waitStatus都是0,在真正进入park之前,会将其前节点waitStatus设置为-1即shouldParkAfterFailedAcquire实现,这是为了线程在运行释放锁时,唤醒最近的后继节点,也即表明其后面是存在等待线程的。
解锁:
(1)调用ReentrantLock中的unlock方法,会使用sync核心变量调用AQS中的release方法;
(2)在AQS释放方法release中首先会进行释放锁的预备工作tryRelease方法,主要是在正常加解锁的前提下对state还原为0;
(3)在tryRelease预备工作满足下,开始真正唤醒头节点的后继节点且该后继节点满足等待状态不是0的调用唤醒方法unparkSuccessor;
(4)在唤醒方法unparkSuccessor中,对于状态为超时状态的线程进行过滤且从尾节点往前找到最靠近头结点的后继节点(<=0,正常一般是信号量-1)进行唤醒。
源码逻辑
1、构造方法
//构造方法是使用公平锁类(FairSync)和非公平锁类(NonfairSync)继承Sync类来初始化sync变量,实现AQS关联
//无参构造使用非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//指定设置为公平锁和非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2、非公平锁lock方法(核心)
NonfairSync类(非公平锁类):
final void lock() {
if (compareAndSetState(0, 1))//调用AQS中封装的CAS方法,对state变量进行设置为1
setExclusiveOwnerThread(Thread.currentThread());//CAS操作成功,调用AQS继承的AOS中的方法,将当前线程设置为占用
else
//核心方法acquire,包含三个大的部分+线程是否中断:tryAcquire、addWaiter、acquireQueued和selfInterrupt
acquire(1);//CAS争抢线程资源失败,则再次进行资源获取或者入队等操作(核心)
}
AbstractOwnableSynchronizer类:
/**
* 此抽象类用于将当前线程设置到变量private transient Thread exclusiveOwnerThread;利用模板模式抽取
* AQS类继承AbstractOwnableSynchronizer类
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
AbstractQueuedSynchronizer类:
//封装CAS操作,内部基于Unsafe类的原子操作CAS
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
3、acquire获取资源方法(核心)
/**
* AQS方法acquire包含如下:
* 再次请求获取资源tryAcquire
* 创建等待节点addWaiter
* 入队方法acquireQueued
* 该争抢资源的线程是否设置了中断selfInterrupt
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//tryAcquire是抽象方法且设置为throw new UnsupportedOperationException();强制子类重写该方法
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();//自我进行中断,acquireQueued中将已被标记的中断记号清楚掉,需要重新进行中断
}
NonfairSync类:
//该方法为重写父类AQS中的抽象方法tryAcquire(抛出异常),子类强制重写,不然就会抛异常throw new UnsupportedOperationException()
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);//调用直系父类Sync中的方法
}
Sync类:
//Sync类定义了非公平锁尝试获取资源方法acquires值为1
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();//获取当前线程
//调用AQS中的方法,主要是返回state值,因为state值是私有,必须通过public或者protected修饰方法获取
int c = getState();
if (c == 0) {//根据state值进行当前锁是否被占用 0代表未被占用 其他值代表已被占用
if (compareAndSetState(0, acquires)) {//调用AQS封装的CAS方法进行设置state值
//调用AQS继承的AOS中定义的方法,将当前线程值到exclusiveOwnerThread变量中
//主要是用于当前线程如果在释放前再次获取锁,防止死锁即可重入锁
setExclusiveOwnerThread(current);
return true;
}
}else if (current == getExclusiveOwnerThread()) {//判断是否再次获取锁,当前线程与exclusiveOwnerThread变量对比即可重入锁
int nextc = c + acquires;//锁的次数+1
if (nextc < 0) //防止超过定义的最大值即Integer.Max
throw new Error("Maximum lock count exceeded");
setState(nextc);//调用AQS中定义的setState方法,对state设值,不需要原子操作,当前没有线程在竞争
return true;
}
return false;//不满足上述两种条件,代表是在竞争锁资源且当前线程竞争失败
}
4、addWaiter创建等待者方法(核心)
AQS类:
/**
* 增加一个等待者==》当前线程node且与队列连接
* addWaiter方法是基于前面tryAcquire方法获取锁资源失败的情况下进行执行
* mode = Node.EXCLUSIVE(互斥模式),该值默认值是独占锁节点为null
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);//基于当前线程创建Node节点
Node pred = tail;//获取当前队列的尾节点
if (pred != null) {//判断是否有被初始化
node.prev = pred;//将当前新生Node节点的前节点为原队列中的尾节点
if (compareAndSetTail(pred, node)) {//利用CAS对尾节点tail进行设置新的节点值
pred.next = node;//队列中的前后节点连接,原节点的下一节点为当前节点
return node;
}
}
enq(node);//tail节点为null时,代表未被初始化,进行头尾节点初始化即队列初始化操作
return node;
}
/**
* 基于当前线程的node,利用自旋和CAS对队列进行初始化即创建哨兵节点和新节点连接
*/
private Node enq(final Node node) {
for (;;) {//自旋操作
Node t = tail;//获取尾节点
if (t == null) { //尾节点是空则进行初始化
if (compareAndSetHead(new Node()))//新建一个空的哨兵节点设置为头节点
tail = head;//尾节点也是指向哨兵节点
} else {//已被初始化
node.prev = t;//当前线程的前节点为当前尾节点
if (compareAndSetTail(t, node)) {//利用CAS尾节点重新赋值
t.next = node;//原尾节点的下一节点为当前node节点
return t;
}
}
}
}
5、acquireQueued入队等待方法(核心)
AQS类:
/**
* 再次验证是否需要进入真正的等待即park
* node为新创建的等待着节点 arg为1代表争抢线程资源
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;//当前node线程默认取消获取资源
try {
boolean interrupted = false;//默认中断标记为false
for (;;) {
final Node p = node.predecessor();//获取当前节点的前节点
if (p == head && tryAcquire(arg)) {//当前节点的前节点为头节点后,尝试获取下锁资源
setHead(node);//设置当前线程为头结点
p.next = null; // help GC
failed = false;//表示当前线程不需要取消获取锁资源
return interrupted;//跳出循环并返回,根据线程是否需要中断,返回true或false
}
//当前线程的前节点设置信号量和当前线程真正意义上的阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;//满足上述if条件则表明中断标记即放弃获取资源,此处是可中断的真正用处
}
} finally {
//只有当try中出现异常时会执行finally代码即在doAcquireInterruptibly中会显式跑出异常,
// doAcquireInterruptibly和acquireQueued代码基本相同,此处也是为了防止try突然出现一些其他原因导致程序异常的最后保障
if (failed)//经过上述try逻辑,判断是否需要取消当前线程的锁资源
cancelAcquire(node);//取消获取锁资源,方式如队列中跳过该节点或者将当前节点设置为超时状态
}
}
//该方法为将当前节点的前节点线程等待值由0->-1,代表可唤醒状态
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)//pred作为当前线程的前节点,判断是否等于-1即Unpark信号量
return true;
if (ws > 0) {//线程被取消时跳过该线程获取锁
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;//跳过该线程获取锁资源
} else {
//对于前节点的线程是初始值0、-2(某种条件)、-3(共享模式)进行设置为-1,代表信号量
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;//除了上述第一种情况代表true以外,其他情况代表本次执行仅仅是调整节点或者设置信号量值
}
//基于前面的shouldParkAfterFailedAcquire返回true的情况下,该方法是真正意义上的park,线程阻塞
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//线程阻塞
return Thread.interrupted();//线程如果没有中断标记,则返回false,反之则返回true即该方法是清楚线程中断标记
}
6、cancelAcquire取消线程获取方法(非核心)
//基于当前节点去处理节点中未取消状态的节点,不是指中断原理唷
private void cancelAcquire(Node node) {
if (node == null)
return;
// 将当前节点封装的线程设置为NULL
node.thread = null;
// 通过循环获取当前节点不为CANCELLED状态的前驱节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// 获取前驱节点的后继节点(如果节点的前驱节点不是CANCELLED状态,那么前驱节点的后继节点就是它自己)
Node predNext = pred.next;
// 将节点的等待状态设置为CANCELLED
node.waitStatus = Node.CANCELLED;
//如果当前节点是尾节点,则直接通过CAS将tail指针指向当前节点不为CANCELLED状态的前驱节点,
//同时通过CAS将前驱节点的后继指针设置为NULL
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
//如果当前节点的前驱节点不是头节点且前驱节点的等待状态为SIGNAL(如果不是SIGNAL那就
//设置为SIGNAL)且前驱节点封装的线程不为NULL
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
// 获取节点的后继节点
Node next = node.next;
// 如果后继节点的等待状态不为CANCELLED,则通过CAS将前驱节点的后继指针指向当前节点的后继节点
if (next != null && next.waitStatus <= 0)
// 这里并没有将当前节点的后继节点的前驱指针指向前驱节点(不用设置,unparkSuccessor()方法会自动跳过)
compareAndSetNext(pred, predNext, next);
} else {
// 如果当前节点的前驱节点是头节点,则直接唤醒当前节点的后继节点,让它来剔除当前节点
unparkSuccessor(node);
}
node.next = node;
}
}
解锁源码逻辑
1、unlock解锁
ReentrantLock类:
public void unlock() {
sync.release(1);//核心成员变量sync调用AQS中的release方法
}
2、release或tryRelease方法
AQS类:
public final boolean release(int arg) {
if (tryRelease(arg)) {//尝试调用释放锁方法即为真正释放锁做准备
Node h = head;//头结点获取
//头结点被初始化和头结点的等待状态是被改变过,主要是在acquireQueued方法中会在当前线程对前节点进行改变:0=>-1
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//满足唤醒被阻塞的等待线程
return true;
}
return false;
}
Sync类:Sync继承于AQS类,父类定义了tryRelease抽象方法并抛出异常,强制子类重写
//此方法主要是为真正释放锁做预备工作,是否正常能将state值还原为0
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//使用state值-1的值给到更新state值
if (Thread.currentThread() != getExclusiveOwnerThread())//保证当前释放的线程是锁的拥有线程
throw new IllegalMonitorStateException();
boolean free = false;//默认释放标志失败
if (c == 0) {//正常释放后,state值是为0
free = true;
setExclusiveOwnerThread(null);//清空锁的占有者
}
setState(c);//更新state值,将1变为初始状态0,作用是为后面真正释放做准备
return free;//返回释放结果即其结果会影响是否真正进行释放
}
3、unparkSuccessor唤醒方法
//唤醒头结点的后继节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;//获取传入的头结点的等待状态
if (ws < 0)//头结点线程等待状态正常情况下一定是-1
//因为拥有锁的线程执行释放逻辑时,等待队列中的头结点要么是哨兵空节点,要么是当前线程即执行锁释放的线程
//等待队列头结点其实是一个占位节点,其身份为:初始化头结点(空节点)或者是拥有锁执行权的线程
compareAndSetWaitStatus(node, ws, 0);//将头节点等待状态设置为0
Node s = node.next;//当前线程的直接后继节点或空节点的直接后继节点
if (s == null || s.waitStatus > 0) {//判断是否存在后继节点或者是超时节点
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)//从队列末尾开始往前寻找最靠前等待状态为<=0的线程
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//唤醒最靠前且等待状态为<=0的等待线程
}