AbstractQueuedSynchronizer简称AQS,即队列(CLH队列)同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是JUC并发包中的核心基础组件
下面先简单的介绍下AQS中比较重要的方法
//同步状态,使用者可以对这个状态进行自定义,一般情况下可以定义为
//0表示没有竞争,线程可以直接获取锁
//大于0的时候锁正在被占用
//对于可重入的独占锁,可以表示为重入的次数
//对于有凭证数量的共享锁,可以表示为凭证数量,获取一个减去1
private volatile int state;
//使用CAS设置state状态,该方法能够保证状态设置的原子性;
protected final boolean compareAndSetState(int expect, int update)
//独占式尝试获取同步状态,需要使用者重写,返回true表明获取成功
protected boolean tryAcquire(int arg)
//独占式尝试释放同步状态,需要使用者重写
protected boolean tryRelease(int arg)
//共享式尝试获取同步状态,需要使用者重写,返回大于等于0表示获取成功
protected int tryAcquireShared(int arg)
//共享式尝试释放同步状态,需要使用者重写
protected boolean tryReleaseShared(int arg)
//独占式获取同步状态,会调用tryAcquire(int arg)尝试获取,如果失败将会将当前线程加入同步队列
public final void acquire(int arg)
//与acquire(int arg)功能相同,该操作可被中断,抛出InterruptedException
public final void acquireInterruptibly(int arg)
//独占式获取同步状态,带上超时机制,如果nanosTimeout纳秒内获取同步状态返回true,否则返回false
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
//共享式获取同步状态,因为不是独占式的,所以可以有多个线程获取同步状态
public final void acquireShared(int arg)
//共享式获取同步状态,可中断
public final void acquireSharedInterruptibly(int arg)
//共享式获取同步状态,带上超时机制
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
//独占式释放同步状态,会调用tryRelease(int arg),如果失败返回false,成功将会唤醒队列中的第二个节点,第一个是头节点(一个空节点)
public final boolean release(int arg)
//共享式释放同步状态,会调用tryAcquireShared(int arg),如果失败返回false,成功将会唤醒队列中的第二个节点
public final boolean releaseShared(int arg)
AQS是一个队列同步器,它的工作只是在并发的环境下处理线程(同步状态竞争失败的线程)入队、线程挂起、线程唤醒、线程出队, 而涉及到需要同步获取状态的逻辑需要使用者自己去编写
以独占模式为例,获取同步状态的过程
从上图可以看出同步器的设计是基于模版方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模版方法,而这些模版方法将会调用使用者重写的方法
在上图中模版方法是acquire
用户需要重写的方法是tryAcquire
以独占模式为例,释放同步状态的过程
下面开始源码部分,先看下Node类
//队列节点,存储等待同步状态的线程,以及一些状态
static final class Node {
//表明该节点中的线程等待的是共享式同步
static final Node SHARED = new Node();
//表明该节点中的线程等待的是独占式同步
static final Node EXCLUSIVE = null;
//表明该节点被取消,按照顺序检查需要被唤醒的线程的时候会跳过该节点
static final int CANCELLED = 1;
//因为是CLH队列,节点会循环访问前驱节点的状态,以确定自身是否可以成功被唤醒,
//当在前驱节点状态为SIGNAL时,表示自己可以放心挂起,等待前驱节点释放同步状态的时候将自己唤醒
//详情见shouldParkAfterFailedAcquire
static final int SIGNAL = -1;
//condition时使用,暂时略
static final int CONDITION = -2;
//表示下一个acquireShared应该无条件传播,具体后面分析
static final int PROPAGATE = -3;
//存储的线程
volatile Thread thread;
//和condition共用,在这里表示同步的模式 为null(独占)或者 SHARED(共享)
Node nextWaiter;
}
//队列头节点,在这里头节点是一个空节点,在线程第一次入队的时候初始化,
//线程出队的时候会把自身节点设置成新的头节点来替代老的头节点
private transient volatile Node head;
//队列尾节点
private transient volatile Node tail;
独占模式
public final void acquire(int arg) {
//先调用用户重写的tryAcquire,如果失败则将本线程放入同步队列中
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//指明需要用户继承重写
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
匹配上面的流程图,这里先调用tryAcquire,如果失败再执行入队操作
看下入队操作的源码
//创建节点,传入节点的类型,这里是独占模式,所以传入Node.EXCLUSIVE
private Node addWaiter(Node mode) {
//调用构造方法,赋值当前线程、节点类型
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//当尾节点不为空,说明队列已经初始化过了,那么直接通过cas将节点加入尾节点
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果尾节点为null 或者 cas失败了
enq(node);
return node;
}
//队列未初始化,或者cas入队失败的情况下调用该方法
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//如果未初始化队列,创建head,cas赋值
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
//如果是之前cas入队失败,这里循环cas 直到成功
//需要注意的细节,先赋值prev,再通过cas赋值next,这样保证了无论cas成功还是失败,队列都是完整的
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
入队完成后,独占模式下,需要对已入队的节点进行一系列的处理
//对已入队的线程进行循环处理(不响应interrupted)
//1、判断自己的前驱是否是头节点,如果是执行tryAcquire,成功则获取同步状态出队,否则2
//2、修改自己前驱状态为SIGNAL(如果前驱状态为cancel则略过),成功则将自己挂起等待下次唤醒,否则3
//3、如果前面都是否,说明在此期间节点发生了变化,那么进入循环进行下一轮判断
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;
}
//修改自己前驱状态为SIGNAL,处理cancel节点,如果成功了
//执行parkAndCheckInterrupt,将自己挂起,等待唤醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果parkAndCheckInterrupt也是true,说明该线程是通过中断唤醒的,那么保存中断标示
interrupted = true;
}
} finally {
//如果失败,比如node.predecessor会抛出NullPointerException
if (failed)
//将节点设置成CANCELLED
cancelAcquire(node);
}
}
//修改前驱状态为Node.SIGNAL,确保自己被挂起后,前驱能够叫醒自己
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//如果已经是SIGNAL,则直接返回
if (ws == Node.SIGNAL)
return true;
//如果>0,说明是cancel,那么这些节点将会被剔除出队列
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果前驱状态不为SIGNAL,则进行cas修改
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//如果前驱状态不为SIGNAL,则返回进入下一个循环
return false;
}
//需要注意的是park是响应中断的,这个时候需要将中断标示保存下来返回
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
//对节点执行取消操作
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
//如果前驱节点也是cancel,剔除出队列
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
//如果是尾节点则通过cas将自身剔除出队列
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
//如果前驱节点不为头节点 而且状态为signal 或者可以被cas修改成signal,后继节点不为cancel
//则尝试将前驱节点直接关联后继节点,将自身剔除出队列
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//如果修改signal失败,或者为头节点,则唤醒后继节点
//这个时候唤醒对后继节点会执行shouldParkAfterFailedAcquire
//来剔除cancel节点和设置前驱节点状态为SIGNAL
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
到这里acquire部分就基本说完了,下面来看下独占模式下释放同步状态
public final boolean release(int arg) {
//这里tryRelease也是需要使用者自己重写对
if (tryRelease(arg)) {
Node h = head;
//如果头节点状态为0,说明后面没有节点了,否则唤醒后继节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//唤醒node的后继节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
//先要修改节点状态,正常情况下这里ws状态应该是SIGNAL,标示后继节点需要唤醒
//而这里已经在进行唤醒操作,所以修改成0
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//当遇到后继节点为空或者为cancel的情况下
if (s == null || s.waitStatus > 0) {
s = null;
//从tail开始循环往前找最前一个状态不为cancel的节点
//至于为什么不从前往后找,因为addWaiter中是先设置node.prev = pred;再设置pred.next = node;
//所以从前往后找,也许找不到这个节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//将后继节点唤醒,这个时候被唤醒的线程开始继续走下一个循环,详细看acquireQueued
if (s != null)
LockSupport.unpark(s.thread);
}
关于acquireInterruptibly, 与acquire的区别是,前者是响应中断,会抛出InterruptedException
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
//和acquireQueued逻辑差不多
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);
}
}
关于tryAcquireNanos,可以理解成带上超时机制的acquire,而且响应中断
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
//大致逻辑还是差不多的
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();
//如果时间小于0,说明已经超时了 直接退出
if (nanosTimeout <= 0L)
return false;
//如果剩余时间大于1000纳秒就用超时机制的挂起
//如果剩余时间小于1000,就直接进入自旋模式,进入下一个循环继续判断
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
//响应中断
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
//如果超时了,那么该节点就变成cancel了
if (failed)
cancelAcquire(node);
}
}
这里基本上将独占模式的获取同步状态、释放同步状态讲完了,接下来看看共享模式部分
先来看看共享式获取同步状态
//和独占模式的区别在于,这里tryAcquireShared返回的不是boolean值了,小于0被认为同步失败
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//逻辑部分和独占模式一样
private void doAcquireShared(int arg) {
//添加节点模式为SHARED
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
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);
}
}
//这个方法做了俩件事
//1、设置传入的节点为头节点
//2、查看头节点的状态如果是signal或者PROPAGATE,则继续唤醒下一个节点
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
// 虽然是共享锁,但是为了避免不必要的唤醒,
// 只有当头节点是signal或者PROPAGATE状态时才唤醒下一个节点
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
//判断下一个节点是否是共享节点
if (s == null || s.isShared())
//唤醒下一个节点
doReleaseShared();
}
}
共享模式允许多个线程同时获取同步状态,所以在队列中等待的线程在被唤醒并且成功获取同步状态的时候,会换新下一个节点, 这就是PROPAGATE的含义
最后来看下doReleaseShared部分
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//当状态为SIGNAL时,表示可以唤醒下一个节点
if (ws == Node.SIGNAL) {
//这里通过cas设置为0是为了防止多个线程同时执行了unparkSuccessor
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
//
unparkSuccessor(h);
}
//如果发现已经有线程将状态设置为0了,则设置成PROPAGATE
//如果设置不成功则循环继续设置,也就是说这里头节点的状态最终只会是SIGNAL或者是PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//当发现头节点没有变过,就可以安全的退出了
if (h == head) // loop if head changed
break;
}
}