JUC锁:核心类 AQS(AbstractQueuedSynchronizer)
AQS是一个用于实现依赖FIFO等待队列的阻塞所和相关同步器的基类。有两个模式:exclusive(独占) 和share(共享) 子类可以实现其中的一种也可以两种都支持
AQS 使用一个int成员变量表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。
成员属性
//等待队列的头部,延迟初始化。除初始化外,仅通过 setHead 方法进行修改。注意:如果 head 存在,则保证其 waitStatus 不是CANCELLED
private transient volatile Node head;
//等待队列的尾部,延迟初始化。仅通过方法 enq 修改以添加新的等待节点
private transient volatile Node tail;
//同步状态,共享变量,使用volatile修饰保证线程可见性。使用CAS对该同步状态进行原子操作
private volatile int state;
state 操作方法
//返回当前同步状态的值
protected final int getState() {
return state;
}
//设置同步状态值
protected final void setState(int newState) {
state = newState;
}
//使用CAS操作修改同步状态。如果当前状态值等于预期值,则原子的将同步状态设置为给定的更新值
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
静态内部类
等待队列Node 类
作为AQS类的底层数据结构,CLH队列是一个虚拟的双向队列(虚拟双向队列及不存在队列实例,仅存在节点之间的关联关系)。
如果要加入CLH锁,可以原子性的将其拼接到尾部,如果要出列设置头head字段。CLH队列需要创建一个虚拟的头结点开始(因为如果不存在争用,那么在构件时创建则可能造成浪费。在第一次出现争用的时候构造节点并且设置头指针和尾指针)
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;
//表示当前节点在等待condition,也就是在condition队列中
static final int CONDITION = -2;
//表示当前场景下的后续acquireShared 能够得以执行
static final int PROPAGATE = -3;
// 0 表示普通节点,使用 CAS进行修改
//节点状态
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;
}
// 无参构造 ,用于建立初始化头结点或者 共享节点
Node() { // Used to establish initial head or SHARED marker
}
//创建一个等待队列 构造方法
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
// 用于创建条件队列 构造方法
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
每个被阻塞的线程都会被分装成一个Node节点,放入队列。每个节点包含了一个Thread类型的引用,并且每个节点都存在一个状态:
CANCELLED,值为1 ,表示当前线程被取消
SIGNAL 值为-1 表示后继节点在等待当前线程唤醒。后继节点入队时,会将前置节点的状态更新为SINGNAL
CONDITION 值为-2 ,表示当前节点在等待condition,即在condition queue 中
PROPAGATE 值为 -3 ,表示当前场景下的后续acquireShared 能够得以执行
值为0, 表示当前节点在 sync queue中,等待获取锁
获取资源方法 acquire
//由 子类进行 重写该方法 进行实现
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
//final修饰 不允许子类擅自修改 以独占的方式进行获取锁,忽略中断
public final void acquire(int arg) {
if (!tryAcquire(arg) && //尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
①先尝试获取锁,如果尝试获取锁失败,先向 阻塞队列中添加当前线程的Node节点
private Node addWaiter(Node mode) {
//创建当前线程的node节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//先尝试快速入队,如果失败再尝试完整入队
Node pred = tail; //尾节点
if (pred != null) {
node.prev = pred; //将尾节点作为当前节点的前置节点
if (compareAndSetTail(pred, node)) { //通过CAS操作将当前节点作为尾节点
pred.next = node; //前置节点指向新的尾节点
return node;
}
}
// 如果尾节点为空或者 尝试cas操作修改尾节点失败
enq(node);
return node;
}
- addWaiter 向 阻塞队列队尾添加元素:
- 如果先快速尝试向队列尾部添加元素,如果失败了则调用enq方法 以自旋 + CAS 的方式 尝试进行将当前线程的Node节点添加到列表尾部
②尝试获取锁失败后,在阻塞队列中 节点不断获取资源
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { //自旋
final Node p = node.predecessor(); // 当前节点的前置节点
if (p == head && tryAcquire(arg)) { //前置节点是头结点,并且尝试获取锁成功
setHead(node); //将当前节点设置为头结点
p.next = null; // help GC //将之前的头结点置为null
failed = false;
return interrupted;
}
//这个位置未使用CAS方式不断的尝试获取锁,而是将未排队到的节点进行挂起排队。减少了性能开销
// 如果返回true 说明前置节点也在尝试获取锁,则进行挂起操作
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 首先判断当前节点的前置节点,如果是前置节点,则当前节点可以尝试去获取资源
- 否则调用 shouldParkAfterFailedAcquire 检查和更新未能获取的节点的状态。如果为CANCLE状态的节点进行删除。如果为SIGNAL状态返回true
- 如果前置节点状态为SIGNAL 则将当前节点桶park方法进行挂起
释放资源方法 release
public final boolean release(int arg) {
if (tryRelease(arg)) { //尝试获得锁
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); //通过cas操作修改 head节点状态,并唤醒头结点的后继节点
return true;
}
return false;
}
通过unparkSuccessor 方法,进行唤醒节点。
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
//头结点的状态
int ws = node.waitStatus;
if (ws < 0) //CAS 操作将状态改为0(头结点为虚节点)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
//从尾节点开始搜索,找到waitStatus<=0 并且最靠前的非head的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//进行唤醒
LockSupport.unpark(s.thread);
}
为什么next 节点为null或状态>0的 则从尾节点开始进行遍历, 将有效节点进行前移
如果next节点不为空 调用Unpark 进行唤醒。进行竞争资源
总结
AQS(AbstractQueuedSynchrinizer) ,是一个构建阻塞式锁和相关同步器工具和框架
AQS的核心思想是什么,他是怎么实现的?
- 核心思想
如果被请求的共享资源空闲,则当前请求的资源的线程设置为有效线程,并且将共享资源设置为锁定状态。
如果被请求的资源被占用,那么就通过CLH 队列锁,将阻塞等待线程及被唤醒的线程进行添加到队列中。
通过使用一个被volatile 修饰的int 变量表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。
使用CAS对该同步状态进行原子操作修改。
AQS使用了什么设计模式
AQS底层使用了模版方法,自定义同步器在实现时只需要实现共享资源state的获取和释放的方式即可。具体如何将阻塞线程添加到队列及如何唤醒AQS已经实现。