AQS核心原理分析
AQS简介
AQS位于java.util.concurrent.locks包下,其全称是AbstractQueuedSynchronizer,即抽象队列同步器,是阻塞式锁和相关的同步器工具的框架。
在AQS内部,主要维护了一个基于FIFO(First Input First Output)的等待队列,类似于前面讲到的monitor锁的WaitSet集合。
AQS底层数据结构
同时,双向链表中的每个节点都是对线程的封装,对于每个节点,都分别指向各自的直接前驱节点和直接的后继节点。当然,也可以对任意一个节点进行遍历。
在多线程并发情况下,当线程竞争锁失败后,会被封装成一个Node节点,加入到AQS队列的末尾,当当前获取锁的对象释放锁后,会在AQS队列中唤醒一个被阻塞的线程。
当然,在AQS队列中,还维护了一个被volatile关键字修饰的变量,它的名字叫做state,state,在中文中有状态的意思,因此state,记录了每个线程的状态。当然,其对应有getState方法–获取state状态,setState方法–设置state状态。多线程情况下,可想而知,肯定会出现线程安全问题,但是,but,设置state状态时,会用到CAS乐观锁机制,调用unsafe类中的方法,保证线程的安全,提供了compareAndSetState方法保证了多线程修改state变量的原子性,(CAS乐观锁在前面文章中记录过)如下面的源码:
//使用volatile关键字修饰
private volatile int state;
//getState方法
protected final int getState() {
return state;
}
//setState方法
protected final void setState(int newState) {
state = newState;
}
//compareAndSetState方法
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
回想Synchronized锁中,有WaitSet和EntryList集合,而在AQS中,当然也有条件变量队列,即condition队列。条件变量可以来实现等待,唤醒机制,不同的是,AQS条件变量中,支持多个条件变量。而在synchronized锁中,使用notifyAll()方法,会一次性将所有阻塞的线程全部唤醒。
AQS对底层锁的支持(Node类)
AQS支持独占锁和共享锁两种模式。独占锁是同时只有一个线程能够访问资源,如基于AQS实现的Reentrantlock锁(这个知识后面会详细讲解)。而共享锁可以同时允许多个线程访问资源。
而独占锁和共享锁模式都是在静态内部类Node中定义的,去掉注释后,Node类的源码如下(包括注释):
static final class Node {
static final Node SHARED = new Node();//表示当前线程为共享模式
static final Node EXCLUSIVE = null;//表示当前线程为独占模式
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;//前驱节点
volatile Node next;//后继节点
volatile Thread thread;
Node nextWaiter;
//判断当前节点是否为共享节点
final boolean isShared() {
return nextWaiter == SHARED;
}
//查找前置节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
// Used to establish initial head or SHARED marker,无参构造
Node() {
}
// Used by addWaiter,创建有条件队列的节点
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
// Used by Condition ,带有初始waitStatus状态的节点
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
解释:
注意:对于AQS,阻塞队列和条件队列是不一样的。阻塞队列是采用双向链表保存的,有pre和next两个属性,而对于上文说的条件队列Condition,是用nextWaiter来链接的,表示当前节点唤醒后,依据该节点的状态,判断是以共享模式,还是以独占模式,其他条件等方式来唤醒下一个线程(节点)。
CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,表示结束状态,进入此状态后的结点将不会再变化。
SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。其实就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。
CONDITION:值为-2,与条件变量相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态表示当前状况下,有资源能够执行后面的acquireShared操作。
由上面代码可知,每个线程(节点包含五个属性:prev,next,thread,waitState,nextWaiter)。其中,变量waitStatus则表示当前被封装成Node结点的等待状态,共有4种取值,默认为0CANCELLED、SIGNAL、CONDITION、PROPAGATE,表示当前线程(节点)处于sync同步队列中,等待获取锁资源。
因此,waitState状态有三种:
- 当waitState>0时,表示该线程处于取消状态(线程中断或者等待锁超时),需要移除线程;
- 当waitState=0时,默认值,表示初始化状态,表示线程还未完成初始化操作;
- 当waitState<0,表示有效状态,该线程处于可唤醒状态。
阻塞队列的运行
当有新线程插入队列时,如果插入的队列为null,插入队列后为头结点,则会直接获取资源,开始执行线程,如果不是头结点,在多线程并发情况下,前驱节点进行CAS自旋操作,直到前驱节点执行完成,waitState状态修改为CANCELLED,断开前驱节点的连接,保证原子性更新尾部的节点,获取到资源执行线程。总的来说,头部节点获取资源执行任务,后续节点通过CAS自旋操作查询前面节点是否完成执行,直到头部节点执行完自己的任务,并将waitState状态修改后,通知后续节点获取资源执行任务。
本篇文章就分享到这里了,后续将会分享各种其他关于并发编程的知识,感谢大佬认真读完支持咯 ~
文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起讨论🍻
希望能和诸佬们一起努力,今后进入到心仪的公司
再次感谢各位小伙伴儿们的支持🤞
- 点赞
- 收藏
- 关注作者
评论(0)