什么是AQS
AQS全称为AbstractQueuedSynchronizer,翻译过来就是抽象队列同步器。
AQS是一个用来构建锁和其他同步组件的基础框架,使用AQS可以简单且高效地构造出应用广泛的同步器,例如我们后续会讲到的ReentrantLock、Semaphore、ReentrantReadWriteLock和FutureTask等等。
AQS实现的核心思想
如果被请求的共享资源(即锁)空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。(为了方便理解以下的共享资源都用锁替代)
如果被请求的锁被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
AQS中的锁是使用一个int成员变量来表示同步状态(即锁被占用与否用一个int变量来表示),通过内置的FIFO队列来完成获取锁线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。状态信息通过procted类型的getState,setState,compareAndSetState进行操作。
// 同步状态,使用volatile来保证其可见性
// 在 ReentrantLock 中 state = 0表示无锁状态
// 在 Semaphore 和 CountDownLatch 中 state 表示锁的个数
private volatile int state;
// 获取同步状态
protected final int getState() {
return state;
}
// 设置同步状态
protected final void setState(int newState) {
state = newState;
}
// 原子性地修改同步状态
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
CLH队列
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求锁的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。当锁被某个线程占有,其他请求该锁的线程会被阻塞,从而进入同步队列。
注:Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。而Condition queue,即等待队列,不是必须的,它是一个单向链表,只有当使用Condition时,才会存在此单向链表,并且可能会有多个Condition queue。
Node节点
节点的类型是AQS的静态内部类Node,代码如下:
static final class Node { // 模式,分为共享与独占 // 共享模式 static final Node SHARED = new Node(); // 独占模式 static final Node EXCLUSIVE = null; // 节点状态值:1,-1,-2,-3 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; } // 获取前驱节点,如果为null,则抛异常 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 } // 有参构造函数1,将线程构造成一个Node,添加到等待队列 Node(Thread thread, Node mode) { // Used by addWaiter this.nextWaiter = mode; this.thread = thread; } // 有参构造函数2,这个方法会在Condition队列使用,后续单独写一篇文章分析condition Node(Thread thread, int waitStatus) { // Used by Condition this.waitStatus = waitStatus; this.thread = thread; } }
Node节点的状态有如下四种
1. CANCELLED = 1
表示当前节点从同步队列中取消,即当前线程被取消2. SIGNAL = -1
表示后继节点的线程处于等待状态,如果当前节点释放锁会通知后继节点,使得后继节点的线程能够运行3. CONDITION = -2
表示当前节点在等待condition,也就是在condition queue中4. PROPAGATE = -3
表示下一次共享式同步状态获取将会无条件传播下去
锁的共享方式
AQS定义两种锁共享方式
1. Exclusive(独占):只能有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
- 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
- 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
2. Share(共享):多个线程可同时执行,如Semaphore、CountDownLatch等。
AQS的设计模式
AQS的设计是使用模板方法设计模式,它将一些方法开放给子类进行重写,而同步器给同步组件所提供模板方法又会重新调用被子类所重写的方法。
自定义同步器时需要重写下面几个AQS提供的模板方法:
// 该线程是否正在独占锁。只有用到condition才需要去实现它。
isHeldExclusively();
// 独占方式。尝试获取锁,成功则返回true,失败则返回false
tryAcquire(int);
// 独占方式。尝试释放锁,成功则返回true,失败则返回false。
tryRelease(int);
// 共享方式。尝试获取锁。负数表示失败;0表示成功,但没有剩余可用锁;正数表示成功,且有剩余锁。
tryAcquireShared(int);
// 共享方式。尝试释放锁,成功则返回true,失败则返回false。
tryReleaseShared(int);
上面的方法均使用protect修饰&