一、概述
AbstractQueuedSynchronizer(AQS)队列同步器,是用来构建其它同步组件的基础框架,同步组件也是构建各种Lock工具(例如:ReentrantLock)的必备组件
例如:下面的自定义锁MyLock的伪代码
public class MyLock implements Lock {
// 自定义的同步组件
private static class MySync extends AbstractQueuedSynchronizer {
// 根据需要重写AbstractQueuedSynchronizer模板类中的方法
@Override
protected boolean tryAcquire(int arg) {
...
}
@Override
protected boolean tryRelease(int arg) {
...
}
@Override
protected boolean isHeldExclusively() {
...
}
...
...
...
}
// 同步组件
private final MySync sync = new MySync();
// 实现Lock接口的基本方法
@Override
public void lock() {
// acquire()会调用自定义同步组件重写的tryAcquire()方法
sync.acquire(1);
}
...
...
...
}
自定义锁时,基本就是按照上面的框架来
自定义Lock类的逻辑就像是Controller-Service框架,Lock是Controller层负责根据请求判断调用什么方法,自定义的同步组件 MySync就像是Service层,实现具体的业务逻辑
AbstractQueuedSynchronizer类一个模板类,采用模板方法设计模式
AbstractQueuedSynchronizer类将大量与锁相关的通用的代码提炼出来,留下不通用的部分提供接口供子类去实现,子类通过重写部分方法实现不同功能的同步器,从而实现不同的锁
二、AQS主要成员
2.1 AQS主要成员属性
AQS内部成员中主要维护了一个表示锁状态的变量state和一个以双向链表实现的同步队列,该链表具有尾指针
private volatile int state;
// 同步队列头结点
private transient volatile Node head;
// 同步队列尾节点
private transient volatile Node tail;
2.2 内部类Node
同步队列中的元素,将thread作为Node中属性封装成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; // 线程等待condition唤醒
static final int PROPAGATE = -3; // 和共享锁有关
// 当前结点中线程处于什么状态
// 1,0,-1,-2,-3五个值。分别对应上面的值,0表示初始状态
volatile int waitStatus;
volatile Node prev;
volatile Node next;
// 当前node绑定的thread
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;
}
...
...
...
}
三、AQS主要方法
AQS内部有两种锁的框架,一种是独占锁一种是共享锁
独占锁:同一时刻只能有至多一个线程持有锁
共享锁:同一时刻可以有多个线程持有锁
下文以独占锁为例说明主要的成员方法
3.1 获取独占锁
- acquire(int arg)
获取锁的方法,自定义锁上锁时会调用MyLock.lock(),MyLock.lock()内部会调用acquire()
//
public final void acquire(int arg) {
// tryAcquire(arg)是自定义的MySync需要重写的方法,里面实现获取同步状态(锁)的逻辑,方法返回false表示获取锁失败
if (!tryAcquire(arg) &&
// 获取锁失败则会在addWaiter()方法中将线程封装成Node(内部类)对象放入同步队列中
// acquireQueued()会让此次放入同步队列的结点不停自旋尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- addWaiter(Node mode)
将当前线程封装成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;
// tail不为空说明同步队列中有还在等待的线程
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) { // CAS修改tail指针指向node
pred.next = node;
return node;
}
}
// tail为空说明同步队列为空,enq()初始化队列
enq(node);
return node;
}
- enq(final Node node)
初始化同步队列
private Node enq(final Node node) {
for (;;) { // 不停自旋直到初始化成功
Node t = tail;
// 这里还要再判断一次tail是否为空
// 因为其它线程可能在当前线程进入enq后,也进入enq并且率先完成初始化
if (t == null) {
if (compareAndSetHead(new Node())) // CAS设置防止其它线程抢先初始化同步队列
tail = head; // 初始化成功则将头尾指针指向nodeshu
} else { // 进入此分支说明其它线程率先完成初始化
// 下面则将当前线程生成的node放到队列尾部而不是初始化队列
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
看到这里不禁感叹,源码这里写得确实漂亮,考虑的非常细致滴水不漏
- acquireQueued(final Node node, int arg)
获取资源失败后使线程不断自旋
在每次自旋失败后判断线程是否需要被挂起,为什么会出现需要被挂起的情况会在下面的shouldParkAfterFailedAcquire()中说明
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); // 获取node的前驱结点
// p == head表示前驱结点是头结点,也就是当前获取锁的结点
if (p == head && tryAcquire(arg)) { // 前驱节点是head就尝试才会自旋获取锁
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// shouldParkAfterFailedAcquire(p, node):判断在本次自旋失败后是否需要park()进入WAITING状态
// parkAndCheckInterrupt():让线程WAITING
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
从源码可以很清楚的看到只有前驱节点为head时,才有机会去获取锁,因此如果自定义的组件不重写逻辑的话,直接使用acquire()这个方法,最终会调用acquireQueued()方法,这样一种机制其实会导致所有线程获取锁的状态时"公平的",先加入队列的线程优先获取锁,锁获取是有顺序的,这就是公平锁
- shouldParkAfterFailedAcquire(p, node)
判断当前线程是否需要被挂起,分以下几种情况:
- 前驱节状态为SIGNAL时:前驱节点释放资源后会唤醒后续节点,也就是本线程,所以本线程可以放心的挂起,等待被唤醒
- 前驱节点状态 > 0时(CANCELLED): 一直往前找,找到第一个状态<0的(有效的线程)
- 除上述两种情况:则CAS将前驱节点状态设置为SIGNAL
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 获取前驱节点状态
if (ws == Node.SIGNAL) // 前驱节点为SIGNAL状态,则其释放资源后
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
可以看到,一般情况下一个线程最多自旋两次,第一次将前驱节点设置为SIGNAL状态,第二次自旋会使自身沉睡
- parkAndCheckInterrupt
线程挂起
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
LockSupport定义了一组静态方法,提供最基本的线程阻塞和唤醒的功能
LockSupport.park()阻塞当前线程
3.2 释放独占锁
锁调用unlock()方法,内部调用同步组件release()方法
- release(int arg)
由于独占锁只有head节点的线程占有锁,所以只需释放head节点
public final boolean release(int arg) {
if (tryRelease(arg)) { // tryRelease(arg)尝试释放锁
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒后继结点
return true;
}
return false;
}
- unparkSuccessor(Node node)
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// 找到head后第一个有效的后继结点
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒后继节点
}
源码分析可以看到,单个的自定义同步组件MySync无法同时支持公平与非公平机制,因此如果自定义的锁要支持两种机制,需要在MyLock内定义两个同步器,分别实现公平锁和非公平锁的逻辑
四、自定义简易独占锁示例
public class MyLock implements Lock {
final class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if(arg == 1) {
if(compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread()); // 设置当前拥有锁的线程(用以支持重入锁)
return true;
}
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if(getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(arg); // 设置为无锁状态setState(0);
return true;
}
}
AbstractQueuedSynchronizer mySync = new MySync();
@Override
public void lock() {
mySync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
mySync.release(0);
}
@Override
public Condition newCondition() {
return null;
}
}
可以看到有了AQS自定义锁及其简单,而锁的种类按照不同特性可以分为:
- 可重入/不可重入锁:构建可重入锁时,对状态变量 state执行+1的动作,获取锁成功;非获取锁的线程想要获取时加入阻塞队列尾部
- 公平/非公平锁
- 独占/共享锁:通过state的数值来区分,独占锁在state > 0时就不允许再获取锁,共享锁可以在state < n时获取锁,n为允许共享的线程数
上述锁可以通过改写AQS逻辑,或者通过几个自定义的同步组件组合形成新的锁