AQS原理与源码剖析(结合ReentrantLock源码)
介绍:AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架
2. AQS数据结构
§ 队列概述
AQS包含两种队列:同步队列和条件队列,实现都如下:
维护一个由双向链表实现的等待队列,链表元素被封装为一个Node对象(其中包含线程信息),这些节点都尝试通过CAS获取/修改state。
节点的详细信息如下:
注意:head节点(队头)是一个虚节点(thread字段为null
),仅保留waitStatus属性供后继节点做相应的判断。它代表其中的线程正在工作。
§ 同步队列属性
当多个线程都来请求锁时,某一时刻有且只有一个线程能够获得锁(排它锁),那么剩余获取不到锁的线程,都会到同步队列中去排队并阻塞自己,当有线程主动释放锁时,就会从同步队列头开始释放一个排队的线程,让线程重新去竞争锁。
所以同步队列的主要作用是***阻塞获取不到锁的线程,并在适当时机释放这些线程。***
同步队列底层数据结构是个双向链表,我们从源码中可以看到链表的头尾,如下:
// 同步队列的头。
private transient volatile Node head;
// 同步队列的尾
private transient volatile Node tail;
§ 条件队列属性
条件队列和同步队列的功能一样,管理获取不到锁的线程,底层数据结构也是链表队列,但条件队列不直接和锁打交道,但常常和锁配合使用,是一定的场景下,对锁功能的一种补充。
条件队列的属性如下:
// 条件队列,从属性上可以看出是链表结构
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
// 条件队列中第一个 node
private transient Node firstWaiter;
// 条件队列中最后一个 node
private transient Node lastWaiter;
}
ConditionObject 我们就称为条件队列,我们需要使用时,直接 new ConditionObject () 即可。
§ Node
static final class Node {
/**
* 同步队列单独的属性
*/
static final Node SHARED = new Node(); // node 是共享模式
static final Node EXCLUSIVE = null; // node 是排它模式
// 当前节点的前节点
// 节点 acquire 成功后就会变成head
// head 节点不能被 cancelled
volatile Node prev;
// 当前节点的下一个节点
volatile Node next;
/**
* 两个队列共享的属性
*/
// 表示当前节点的状态,通过节点的状态来控制节点的行为
// 普通同步节点,就是 0 ,条件节点是 CONDITION -2
volatile int waitStatus;
// waitStatus 的状态有以下几种
// 被取消
static final int CANCELLED = 1;
// SIGNAL 状态的意义:同步队列中的节点在自旋获取锁的时候,如果前一个节点的状态是 SIGNAL,那么自己就可以阻塞休息了,否则自己一直自旋尝试获得锁
static final int SIGNAL = -1;
// 表示当前 node 正在条件队列中,当有节点从同步队列转移到条件队列时,状态就会被更改成 CONDITION
static final int CONDITION = -2;
// 无条件传播,共享模式下,该状态的进程处于可运行状态
static final int PROPAGATE = -3;
// 当前节点的线程
volatile Thread thread;
// 在同步队列中,nextWaiter 并不真的是指向其下一个节点,我们用 next 表示同步队列的下一个节点,nextWaiter 只是表示当前 Node 是排它模式还是共享模式
// 但在条件队列中,nextWaiter 就是表示下一个节点元素
Node nextWaiter;
}
从 Node 的结构中,我们需要重点关注 waitStatus 字段,Node 的很多操作都是围绕着 waitStatus 字段进行的。
Node 的 pre、next 属性是同步队列中的链表前后指向字段,nextWaiter 是条件队列中下一个节点的指向字段,但在同步队列中,nextWaiter 只是一个标识符,表示当前节点是共享还是排它模式。
3. AQS维护核心变量–state和waitStatus
- state 是锁的状态,是 int 类型,子类继承 AQS 时,都是要根据 state 字段来判断有无得到锁,比如当前同步器状态是 0,表示可以获得锁,当前同步器状态是 1,表示锁已经被其他线程持有,当前线程无法获得锁;
- waitStatus 是节点(Node,可以理解为线程)的状态,种类很多,一共有初始化 (0)、CANCELLED (1)、SIGNAL (-1)、CONDITION (-2)、PROPAGATE (-3),各个状态的含义可以见上文。
我们可以通过修改state字段实现多线程的独占/共享加锁模式:
4. AQS核心代码
- 首先调用可以由子类重写的
tryAcquire
方法获取资源(修改state)。- 成功则无事,失败则入队
addWaiter(Node.EXCLUSIVE)
。acquireQueued
方法对入队节点进行相应操作使其获取资源/停止获取,中断。
一般来说,自定义同步器要么是独占方式,要么是共享方式,它们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared
中的一种即可。AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。ReentrantLock是独占锁,所以实现了tryAcquire-tryRelease
。
1)acquire()
以下整个过程以排他锁为例
acquire指定了获取锁的框架分为排他锁和共享锁。
// 排它模式下,尝试获得锁
public final void acquire(int arg) {
// tryAcquire 方法是需要实现类去实现的,实现思路一般都是 cas 给 state 赋值来决定是否能获得锁
if (!tryAcquire(arg) &&
// addWaiter 入参代表是排他模式
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
a) tryAcquire()
需要子类重写。
b)addWaiter()
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 1. 先尝试将node插入尾部
if (pred != null) {
// a
node.prev = pred;