定义
AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。
数据结构
它维护了一个state,代表共享资源,一个等待队列,多线程调用资源被阻塞时候就会进入此队列
AQS使用了模板方法模式,自定义的同步器在在实现时只需要实现共享资源state的获取与释放即可,值域具体线程等待队列的维护,AQS已经在顶层实现好了。
模板方法主要有:
独占式获取
- accquire
- acquireInterruptibly
- tryAcquireNanos
共享式获取
- acquireShared
- acquireSharedInterruptibly
- tryAcquireSharedNanos
独占式释放锁
- release
共享式释放锁
- releaseShared
自定义同步器主要是实现以下方法
-
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
-
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
-
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
-
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
-
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false
源码解析
先介绍一下它的变量及内部类Node节点。
/** 队列中的头节点 */
private transient volatile Node head;
/** 队列中的尾节点 */
private transient volatile Node tail;
/** 同步状态、锁数量 */
private volatile int state;
/** 获取同步状态 */
protected final int getState() {
return state;
}
/** 设置同步状态 */
protected final void setState(int newState) {
state = newState;
}
/** 通过CAS设置同步状态 */
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
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;
/** node所属的线程 */
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;
}
}
独占锁
我重点介绍的是独占锁,因为共享锁用到的少。
- 获取独占锁
![](https://i-blog.csdnimg.cn/blog_migrate/426e5e5646ce229a5398d5c27ea4dceb.png)
// 获取锁
public final void acquire(int arg) {
// tryAcquire获取锁,如果state==0,或者持有锁的线程再次获取锁,那么就得到锁并修改state,可重入锁也是在这里提现出来的,由子类具体实现,
// Node.EXCLUSIVE表示创建独占模式节点
// 如果获取不到锁就将当前线程加入到队列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
没有获取到锁,所以将节点加入到队列尾部
// 将节点插入等待队列尾部
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 如果尾节点不为空,则快速将node插入到队列尾部
Node pred = tail;
if (pred != null) {
node.prev = pred;
// CAS设置当前节点为尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 否则,通过自旋方式直到成功
enq(node);
return node;
}
// 自旋将节点插入等待队列尾部
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 如果尾节点为空,说明队列中没有节点,那么就通过CAS初始化队列,并且头、尾节点指向同一个
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 否则将本节点插入队列末尾
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
尝试去获取锁,如果获取不到,那么就进入休眠
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; // 将之前的头结点的next设为null,表示从队列中脱离
failed = false;
return interrupted;
}
// 没获取到锁,那么就进入waiting状态,直到被unpark()
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 线程被中断,那么就改变状态位
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// 由方法名就可以看出是 获取锁失败后应该进入睡眠状态
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 获取前驱节点的状态
if (ws == Node.SIGNAL)
// 如果状态为-1即下一节点需要被唤醒,那么返回true
return true;
if (ws > 0) {
// 如果状态>0即该节点已经被废弃,那么往前遍历,直到找到正常等待的节点,并排在它后面
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它下一节点需要被唤醒
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 线程进入睡眠
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 进入睡眠,等待被唤醒
return Thread.interrupted(); //如果被唤醒,查看自己是不是被中断的
}
/**
* 如果线程发现线程被中断,那么就中断当前线程
*/
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
- 超时等待获取锁
与获取锁逻辑一样,只不过多了时间判断
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;
// 将node添加到队列
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);
}
}
- 释放锁
释放锁逻辑比较简单,大致就是将state-1,唤醒下一个等待线程。
public final boolean release(int arg) {
// tryRelease()将state -1,如果state变为0,则将拥有锁的线程exclusiveOwnerThread置为null,由子类具体实现
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 如果头节点不为空并且状态不是0,那么就去唤醒下一个节点
unparkSuccessor(h);
return true;
}
return false;
}
唤醒下一个节点
// 唤醒下一个没有被废弃的节点
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
// 如果线程等待状态<0,那么就CAS变为0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 如果下一个节点为空或者已经被废弃,则从尾部向前获取到状为<=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);
}
共享锁
读写锁中的读锁,就是实现了共享锁。
- 获取共享锁
public final void acquireShared(int arg) {
// tryAcquireShared尝试获取锁,返回小于0,则说明获取锁失败,由自类实现
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
tryAcquireShared()方法实现可以看 https://blog.csdn.net/zgsxhdzxl/article/details/106071399
没有获取到锁,则
private void doAcquireShared(int arg) {
// 将节点加入到尾部
final Node node = addWaiter(Node.SHARED);
boolean interrupted = false;
try {
for (;;) {
final Node p = node.predecessor();
// 前驱节点是头结点,则去获取锁
if (p == head) {
int r = tryAcquireShared(arg);
// 获取锁成功
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
return;
}
}
if (shouldParkAfterFailedAcquire(p, node))
interrupted |= parkAndCheckInterrupt();
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
} finally {
if (interrupted)
selfInterrupt();
}
}
获取锁成功之后,还会去唤醒后续的共享节点setHeadAndPropagate()
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
// 将node设置为新的首节点
setHead(node);
// 判断后续节点是否需要被唤醒
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 下个节点是共享方式,则唤醒后续为共享方式的节点
if (s == null || s.isShared())
doReleaseShared();
}
}
这里一直没搞明白,为什么会唤醒后续的共享节点,因为感觉循环只会唤醒一个节点。
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 只有结点状态为SIGNAL才尝试唤醒后继结点
if (ws == Node.SIGNAL) {
// 将waitStatus设置为0
if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
continue;
// 唤醒后续的节点
unparkSuccessor(h);
}
// 如果状态为0则更新状态为PROPAGATE
else if (ws == 0 &&
!h.compareAndSetWaitStatus(0, Node.PROPAGATE))
continue;
}
if (h == head)
break;
}
}
- 释放锁
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared可以看 https://blog.csdn.net/zgsxhdzxl/article/details/106071399