概念
首先,AQS是什么?
在官网的解释中,AQS是一个基于 先进先出(FIFO)队列,可以构建其他同步装置(semaphores,events)的基础框架。为大多数依赖于用一个原子int值来表示状态的同步器提供了基础。使用方法为继承,子类通过继承,必须实现父类中的抽象方法来管理其状态。而一般子类是作为同步装置中的内部类,如ReetrantLock中的Sync,AQS不实现任何同步接口,只是提供一些公共的操作方法,各个锁或者同步装置通过调用这些方法来实现自己的设计.
AQS默认支持独占模式和共享模式,当以独占方式获取时,其他线程无法获取成功;以共享模式获取时,其他线程可能成功,当线程获取锁成功时,还必须确定下一个等待线程(如果有的话)能否获取成功,如ReetrantReadWriteLock中的readLock。
源码分析
队列节点Node数据结构
AQS中依赖的是一个队列结构,将等待线程封装在队列节点中,节点结构主要的成员变量如下:
class Node {
//等待状态
volatile int waitStatus;
//前置节点
volatile Node prev;
//下一节点
volatile Node next;
//等待线程
volatile Thread thread;
//下一等待线程,condition队列
Node nextWaiter;
}
注意的是,waitStatus的状态有其特定的值:
SIGNAL(-1) | 后继节点中的线程需要被唤醒(unpark) |
CANCELLED(1) | 由于超时或中断,该节点被取消。 节点永远不会离开此状态。 特别是,具有取消节点的线程永远不会再次阻塞。 |
CONDITION(-2) | 该节点当前在条件队列中。 |
PROPAGATE(-3) | 后续的acquireShared能够得以执行 |
0 | 表示当前节点在sync队列中,等待着获取锁。 |
AQS数据结构
主要成员变量:
private transient volatile Node head;//头结点
private transient volatile Node tail;//尾结点
private volatile int state;//状态
链式结构示意如下:
使用state字段来记录锁状态,并提供三个方法来操作state值:
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
AQS可拓展的几种API
AQS提供5个抽象方法,由继承AQS的子类(如ReentratLock中的Sync)来具体实现
boolean tryAcquire(int arg) | 以独占模式尝试进行获取 状态,若 状态 未被占用,则可以获取,否则不能获取 |
boolean tryRelease(int arg) | 释放锁 |
boolean tryAcquireShared(int arg) | 以共享模式尝试进行获取。 此方法应查询对象的状态是否允许以共享模式获取对象,如果允许则获取对象。 |
boolean tryReleaseShared(int arg) | 释放锁 |
boolean isHeldExclusively() | 独占模式下,状态是否被占用 |
以独占模式为例,其核心的执行逻辑如下:
//独占模式请求锁
while (!tryAcquire(arg)) {//尝试获取锁
//未获取到锁
//1: 若当前线程未入等待队列,则加入等待队列
//2:阻塞当前线程
}
//独占模式释放锁
if (tryRelease(arg))//尝试释放锁
//唤醒等待队列中的线程
看一下官网给出的示例
class Mutex implements Lock, java.io.Serializable {
// 自定义的同步器,继承AQS
private static class Sync extends AbstractQueuedSynchronizer {
// Reports whether in locked state
//是否处于被占用状态
protected boolean isHeldExclusively() {
return getState() == 1;
}
// Acquires the lock if state is zero
//当状态为0时候获取锁
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {//CAS机制设置state值
//设置当前线程为独占线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// Releases the lock by setting state to zero
//释放锁,将状态置为0
protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
//将独占线程设为null
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// Provides a Condition
Condition newCondition() { return new ConditionObject(); }
// Deserializes properly
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
// The sync object does all the hard work. We just forward to it.
private final Sync sync = new Sync();
public void lock() { sync.acquire(1); }
public boolean tryLock() { return sync.tryAcquire(1); }
public void unlock() { sync.release(1); }
public Condition newCondition() { return sync.newCondition(); }
public boolean isLocked() { return sync.isHeldExclusively(); }
public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
AQS提供的常用公共方法
AQS对也提供了几种可以直接调用的方法
void acquire(int arg) :在独占模式下获取,忽略中断。 通过至少调用一次tryAcquire(int)来实现,并在成功后返回。 否则,线程将排队,并可能反复阻塞和解除阻塞,并调用tryAcquire(int)直到成功。
此方法可用于实现Lock.lock()方法。
源代码:
public final void acquire(int arg) {
//tryAcquire(arg)由具体子类实现
if (!tryAcquire(arg) &&
//加入等待队列,不断尝试获取
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
方法执行逻辑如下:
- 调用子类实现的tryAcquire()方法,若成功则直接返回;
- 若调用失败,则构造Node节点,将当前线程封装入节点中并加入等待队列;
- 在队列中循环等待,直到被调度;
构造Node并加入等待队列的具体实现代码如下:
//加入等待队列
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
//有尾节点
if (pred != null) {
node.prev = pred;
//将当前节点置为尾结点
if (compareAndSetTail(pred, node)) {
//将当前节点直接加在前置节点后
pred.next = node;
return node;
}
}
//无尾结点,即队列为空
enq(node);
return node;
}
private Node enq(final Node node) {
//保证线程安全
for (;;) {
Node t = tail;
//无尾节点,即队列为空
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
//将尾节点指向头结点
tail = head;
} else {
//添加当前节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
通过分析源码可知,执行逻辑如下:
- 将当前线程封装入Node节点中;
- 将node加入等待队列中;
加入等待队列操作如下:
- 找到尾结点;
- 若尾结点存在,则将当前节点的前置节点指向当前尾结点( node.prev = pred),将tail的值修改当前节点(compareAndSetTail(pred, node)(此步骤为原子操作,直接操作内存),若修改成功,则将尾结点的后置指向当前节(pred.next = node);
- 若尾结点不存在,或者修改tail失败,若是尾结点为null ,此时队列为空,则分配一个头节点head(原子操作),并将尾结点tail指向头结点。而后重复操作2,直到成功;
那么为什么要在一个for循环体内?
AQS允许多个线程同时执行,但需要保证操作的原子性。基于CAS操作,需要配合死循环使用。这能够保证每个线程都能够正确的进入等待队列。
线程在队列中等待(阻塞),代码如下
//独占模式下且不间断的获取锁
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
failed = false;
return interrupted;
}
//如果线程需要阻塞,则将线程阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
执行逻辑如下:
- 获取当前节点的前驱节点p;
- 若p为头结点head,表示当前线程已是队列中的第一个线程,并尝试获得锁。若成功,则将当前节点置为新的头结点(节点内的pre,thread等成员变量置为空),并将当前头结点中的next置为空(此时旧的头结点head已从队列中脱离,并由垃圾回收器回收);
- 根据node中前置节点中的的waitStatus字段值,当值为SIGNAL时,当前线程阻塞,因为SIGNAL表示当前置节点释放锁时,会唤醒后继节点中的线程;
- 若当前线程不阻塞,循环执行步骤2;
void acquireInterruptibly(int arg):以独占模式获取,如果线程被中断则中止。 通过首先检查中断状态,然后至少调用一次 次tryAcquire(int)来实现,并在成功后返回。 否则,将线程排队,并可能反复阻塞和解除阻塞,并调用tryAcquire(int)直到成功或线程被中断为止。
此方法可用于实现Lock.lockInterruptible()方法。
源代码:
public final void acquireInterruptibly(int arg)
throws InterruptedException {
//检查中断状态
if (Thread.interrupted())
//抛出异常
throw new InterruptedException();
//调用具体子类实现
if (!tryAcquire(arg))
//失败则进入队列
doAcquireInterruptibly(arg);
}
执行逻辑如下:
- 检查线程中断状态,若被中断,则抛出异常,结束;
- 调用具体子类实现tryAcquire(int)方法获取,若成功,则返回,结束;
- 若获取失败,则进入等待队列等待;
此方法在执行步骤上与acquire()类似,然而不同的是,在此方法中可以接受中断信号,对于外界中断能够提前结束获取state的操作。以下是具体实现:
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
//检测中断,抛出异常
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
此方法与acquire方法调用的acquireQueued类似,不同点在于此方法若检测到线程被中断,不再阻塞,提前结束获取state操作,并抛出InterruptedException异常提醒用户。
boolean tryAcquireNanos(int arg, long nanosTimeout):尝试以独占方式进行获取,如果被中断则中止,如果给定的超时时 间过去,则失败。
通过首先检查中断状态,然后至少调用一次tryAcquire(int)来实现,并在成功后返回。 否则,将线程排队,调用tryAcquire(int)直到成功或线程被中断或超时为止。
此方法可用于实现方法Lock.tryLock(long,TimeUnit)。
源代码:
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
执行逻辑:
- 检查线程中断状态,若中断,抛出异常,结束;
- 尝试调用子类实现的tryAcuqire()尝试获取状态,若成功,直接返回;
- 若失败,进入等待队列,若超过nanosTimeout时间仍未获取状态,返回失败;
doAcquireNanos源代码如下:
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
分析源代码,doAcquireNanos()执行逻辑如下:
- 若剩余时间<0,返回false;
- 计算结束时间,当前时间+剩余时间;
- 构造Node节点,封装当前线程,加入等待队列;
- 满足获取状态条件,并尝试获取,若成功,则返回;
- 若获取状态失败或不满足获取条件,将此线程休眠一段时间;
注意的是,当剩余时间小于 spinForTimeoutThreshold(1000L纳秒)时,进入自旋过程;
boolean release(int arg):以独占模式释放。 如果tryRelease(int)返回true,唤醒一个或多个等待线程。
此方法可用于实现Lock.unlock()方法。
源代码:
public final boolean release(int arg) {
//子类实现的tryRelease()方法
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
执行逻辑如下:
- 调用子类实现的tryRelease(arg),释放state;
- 唤醒后继节点中的线程;
唤醒后继线程源代码如下:
//唤醒node节点的后继节点中的线程,如果存在
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
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);
}
执行逻辑如下:
- 找到那个当前节点的后置节点s;
- 若s为空,表示无后续线程,结束;
- 若s不为空,且s节点的状态为CANCEL(取消),则从队列尾结点开始往前寻找第一个符合唤醒条件(waitStatus<=0)的节点,并将其置为s;
- 若此时s不为空,则唤醒s节点中的线程;
void acquireShared(int arg):以共享模式获取,忽略中断。
通过首先至少调用一次tryAcquireShared(int)来实现,并在成功后返回。 否则,将线程排队,并可能反复阻塞和解除阻塞,并调用tryAcquireShared(int)直到成功。
源代码:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
首先调用具体子类实现tryAcquireShared(int),若失败,则进入doAcquireShared(arg)中排队等待,doAcquireShared方法源代码为:
private void doAcquireShared(int arg) {
//构造node节点,封装当前线程,并加入等待队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前节点的前置节点p
final Node p = node.predecessor();
//当p为头结点,此时当前节点可以开始获取状态
if (p == head) {
//尝试获取状态,若r<0,则表示失败,r=0,表示后续没有可以共享获取的线程,r>0,表示后续还有可以共享的线程
int r = tryAcquireShared(arg);
if (r >= 0) {
//重新设置头结点,并通知下一节点获取状态,如果有的话
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//将当前线程暂停,减少开销
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
执行逻辑为:
- 构造node节点,加入等待队列;
- 获取当前节点的前置节点p,若p不是头结点,则当前线程阻塞,以减少for循环开销;
- 当p变为头结点时,当前线程可获得锁,则调用具体子类实现tryAcquireShared()方法,若成功,则更新头结点,并且通知下一节点获取锁。
更新头结点和通知下一节点是调用setHeadAndPropagete()方法,源代码为:
private void setHeadAndPropagate(Node node, int propagate) {
//设置头结点
Node h = head; // Record old head for check below
setHead(node);
//当存在后继节点,且等待状态<0时,唤醒线程,尝试获取锁
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
boolean realseShared(int arg): 以共享模式释放状态;
源代码:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
执行逻辑:
- 调用子类实现的tryReleaseShared(arg)方法;
- 成功后,唤醒后续节点;
步骤2调用doReleaseShared方法,源代码如下:
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
其核心是当满足调用条件,即调用unparkSuccessor(h)方法,唤醒下一节点线程。
总结
AQS是依赖一个int类型的成员变量state来保存锁的状态,并提供对state进行原子操作的三个方法。并且对于不同的模式也提供了不同的操作方法,两种模式(独占模式,共享模式)都对应有阻塞式获取,可被打断的阻塞获取,带有过期时间的阻塞获取(acquire),两种模式也有其对应的释放方法(release)。他们都需要调用AQS中的抽象方法,如tryAcquire...,这些方法为非阻塞式,但是需要由继承的子类根据其设计逻辑来具体实现。