本文内容为个人参照JKD1.8源码注释及资料学习后个人的理解总结,理解错误之处还望勿喷。
1.AQS是什么?
多线程并发访问共享资源时,为了保证数据的安全性,我们采用加锁的手段保证多线程访问共享资源的安全(这里不讨论无锁手段)。锁可以大致分为两种----Synchronized锁和基于Lock,AQS的实现锁。AQS是一个抽象队列同步器(Abstract Queued Synchronizer),用来控制多线程对共享资源的同步操作。常见的锁如:ReentrantLock, ReentrantReadWriteLock,CountDownLatch,StampedLock等都是基于AQS实现的锁。
JDK1.8中注释为:AQS是一个基于FIFO等待队列、用于实现阻塞锁和相关同步器的框架。该类被设计目的是作为其他同步器的基础,这些同步器使用原子类整数代表状态(翻译的不好,将就看)。
* Provides a framework for implementing blocking locks and related
* synchronizers (semaphores, events, etc) that rely on
* first-in-first-out (FIFO) wait queues. This class is designed to
* be a useful basis for most kinds of synchronizers that rely on a
* single atomic {@code int} value to represent state.
AQS抽象类中定义了一个final静态的Node节点和一些原子类的CAS方法方法,如下:
2.AQS类伪代码摘要
AQS在其内部维护了一个Node对象,用于保存竞争竞争同步器失败的线程。同时提供了一些竞争获取同步器方法,这个CAS方法都是基于unsafe的原子方法。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
//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;
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;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
}
//竞争获取同步器方法伪代码摘要
public final void acquire(int arg) {}
final boolean acquireQueued(final Node node, int arg) {}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {}
private void cancelAcquire(Node node) {}
private Node enq(final Node node) {}
private final boolean compareAndSetTail(Node expect, Node update) {}
private final boolean compareAndSetTail(Node expect, Node update) {}
}
3.Node代码学习
以下注释,翻译摘抄自JDK1.8源码注释,刚学习翻译的不准确!!!Node对象定义了不同的状态和方法,用于封装竞争同步器的线程对象。
static final class Node {
//node处于共享模式
static final Node SHARED = new Node();
//node处于独占模式
static final Node EXCLUSIVE = null;
//该状态表明线程被取消了
static final int CANCELLED = 1;
//该状态表明后继线程需要被唤醒
static final int SIGNAL = -1;
//该线程等待在Condition对象上
static final int CONDITION = -2;
//该状态表明下一个共享模式获取应该无条件传播
static final int PROPAGATE = -3;
//等待状态:SIGNAL、CANCELLED、CONDITION、PROPAGATE、0
//SIGNAL:当前节点的后继节点处于阻塞状态(经过park),因此当前节点释放了同步器或者取消后必须唤醒它的后 继节点,为了避免多线程竞争,acquire方法首先需要一个SIGNAL,然后尝试去原子性获取,失败后就阻塞入队。
//CANCELLED:当前节点取消,这是一个终态,该状态下的线程不会再次出现在阻塞队列中。
//CONDITION:当前节点处于CONDITION上,在该状态发生转变前该节点不会作为同步节点。
//PROPAGATE:一个释放的共享状态应该通知传播给所有节点。
//0:非以上状态
volatile int waitStatus;
//前一个节点
volatile Node prev;
//下一个节点
volatile Node next;
//当前线程
volatile Thread thread;
//下一个等待节点,与处于condition上的节点关联。
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;
}
}
4.AQS中重要方法学习。
4.1 获取同步器方法acquire(int args)
以独占模式获取同步器,忽略中断。获取同步器成功时,tryAcquire至少会调用一次。获取同步器失败时,线程会加入到队列中,并且可能会在阻塞和非阻塞状态间多次切换,直到成功获取到同步器。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
4.1.1 tryAcquire(int arg)
AQS未对tryAcquire(int arg)方法做具体实现,仅仅抛了一个异常。具体的实现由继承的子类复写。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
4.1.2 tryAcquire(int arg) 的复写实现方法
我们以AQS的子类Sync(本次看的Sync维护在ReentrantLock类中)同步器中的非公平方法nonfairTryAcquire(acquires)为例看下tryAcquire的实现逻辑。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取同步器的状态,初始状态为0
int c = getState();
if (c == 0) {//state为0表示同步器未被占用,即当前线程可以抢锁
if (compareAndSetState(0, acquires)) {//CAS去尝试设置state
setExclusiveOwnerThread(current);//如果CAS设置state成功,则标记当前线程独占锁
return true;
}
}
//如果同步器已被抢占,检测是否是当前线程自身占用(ReentrantLock允许重入)。
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;//重入次数,释放锁时次数要平衡
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;//竞争锁失败
}
4.2 抢占同步器失败
线程抢占同步器失败时,会调用如下acquireQueued(Node node)方法将当前线程对象封装成一个Node节点添加到同步器队列链表中(FIFO)。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
4.2.1 addWaiter(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设置到链表尾部
Node pred = tail;//取出尾节点pred
if (pred != null) {//尾节点存在的话
node.prev = pred;//将当前Node的前驱节点指向尾节点,即pred
if (compareAndSetTail(pred, node)) {//对比尾节点pred和内存中的预期值,如果一致,则CAS将node设置为新的尾节点
pred.next = node;//旧的尾节点的后继节点设置为当前节点node
return node;
}
}
//如果快速入队失败(队列不存在或者抢占失败)
enq(node);
return node;
}
4.2.2 快速入队失败后入队enq(Node node)
private Node enq(final Node node) {
for (;;) {//自旋,等于while(true){...}
Node t = tail;//尾节点
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))//尾节点为null,cas创建一个新Head头节点,
tail = head;//头节点赋给尾节点,新创建的节点既是头节点也是尾节点
} else {//将node节点追加到尾节点t后面
node.prev = t;//当前节点node的前驱节点指向尾节点t
if (compareAndSetTail(t, node)) {//如果尾节点t当前值和内存中预期值一直,则将当前节点node设置为新的尾节点
t.next = node;//旧的尾节点t的后继节点指向新的尾节点node
return t;
}
}
}
}
4.3 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
4.1方法线程抢占同步器,如果失败则会走4.2方法将线程对象封装成Node节点 ,然后走4.3方法acquireQueued(Node node,arg)尝试从队列中获取节点。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {//自旋
final Node p = node.predecessor();//当前节点node的前驱节点p
if (p == head && tryAcquire(arg)) {//前驱节点p为head && 尝试获取同步器,将状态设置为arg
setHead(node);//当前节点node设置为头节点
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && //获取同步器失败后应该park(休眠)
parkAndCheckInterrupt()) // 休眠检测中断
interrupted = true;//中断
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4.4 shouldParkAfterFailedAcquire(Node pred, Node node)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {//pred前驱节点,node当前节点
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//前驱节点pred的waitStatus为SIGNAL,表明当前节点已经设置了请求pred释放同步器时唤醒自己,node可以安全park了。
return true;
if (ws > 0) {
//waitStatus>0,前驱节点被取消,跳过前驱节点
do {
node.prev = pred = pred.prev;//1.跳过前驱,取出前驱节点的前驱。2.将当前节点node的前驱指向新的前驱节点。
} while (pred.waitStatus > 0);//循环,直到找到一个不是取消状态的前驱节点
pred.next = node;//前驱节点的后继节点设置为当前节点
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//前驱节点的waitStatus必须是0或PROPAGATE,也就是说当前节点park前需要得到一个前驱节点SIGNAL信号。
//当前节点在park之前会尝试确保当前节点不需要再去获取同步器。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//CAS设置前驱节点pred的状态为SIGNAL
}
return false;
}
5.释放同步器
前面步骤看了获取同步器,接下来跟踪学习下释放同步器过程。
AQS中该方法没有具体实现,看下子类实现。
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
5.1 release(int arg) 的一个实现
这里也同样看下ReentranLock中的静态内部类Sync实现,该类继承自AQS,子类复写了tryRelease(int arg)方法。
public final boolean release(int arg) {
if (tryRelease(arg)) {//尝试释放同步器
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//唤醒后继节点
return true;
}
return false;
}
5.2 tryRelease(int releases)
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())//获取同步器和释放同步器应该是同一个线程对象
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//0状态代表同步器未被占用
free = true;
setExclusiveOwnerThread(null);
}
setState(c);//state设置为新值。这里分析ReentranLock是支持重入的,取决于AQS子类的具体实现。
return free;
}
5.3 unparkSuccessor(Node node) 唤醒当前节点后继节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) // -1:后继节点等待唤醒,-2:当前节点等待在condition上,-3:propagate
compareAndSetWaitStatus(node, ws, 0);//CAS将当前节点waitStatus置为0,即别的节点可以来抢占同步器了。
Node s = node.next;//后继节点
if (s == null || s.waitStatus > 0) {//后继节点为null,或者后继节点已取消
s = null;
for (Node t = tail; t != null && t != node; t = t.prev) //逆序查找最后一个waitStatus<0 的节点
if (t.waitStatus <= 0)
s = t;//将其设置为当前节点(即head节点)的后继节点
}
if (s != null)
LockSupport.unpark(s.thread);//真正唤醒方法是调用Native方法
}
总结:AQS的原理图如下:
至此为止,AQS同步器的换取和释放过程原理,大概粗略学习过了一下。基于AQS的有一些已经实现好的锁方法,如:ReentrantLock,ReetrantReadWriteLock,Semaphore,CountDownLatch,ThreadPoolExecutor等。因此,掌握了AQS的基本原理后再学习这些实现框架就会轻松很多,甚至可以写一个自己的锁。
最后,本文内容来自于个人学习JDK中AQS源码后个人感悟总结,有理解错误指出还望口下留情,共同学习。
附:ReentranLock部分代码
下面一段摘自JDK1.8的ReentranLock的部分代码,可以看到ReentrantLock内部维护了一个Sync类,该类extends AQS。ReentrantLock的加锁方法lock(),释放锁unlock()方法均来自于AQS。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
//静态内部类,同步器
abstract static class Sync extends AbstractQueuedSynchronizer { //继承自AQS
}
//静态内部类,非公平同步器
static final class NonfairSync extends Sync {
}
//静态内部类,公平同步器
static final class FairSync extends Sync {
}
//构造一个非公平的同步器
public ReentrantLock() {
sync = new NonfairSync();
}
//根据传入条件,构造一个相应的同步器
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
}