基本概念
AQS 的独占模式,表示 AQS 通过独占模式获取/释放锁。该类对应的方法为 acquire /acquireInterruptibly/tryAcquireNanos、release。
在 AQS 中共有独占、共享两种模式。
acquire
acquire 表示以独占模式获取锁对象,并忽略中断。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
// 设置线程当前线程中断标记位
selfInterrupt();
}
}
private static void selfInterrupt() {
// 只修改线程的中断标记位,线程并不会真正中断,所以又称忽略中断
Thread.currentThread().interrupt();
}
从代码中可以知道,获取锁对象的完整操作如下:
- tryAcquire,表示尝试获取锁,该方法是空方法,留给 AQS 的子类实现
- acquireQueued,表示获取锁失败时则将当前线程包装成节点再添加进等待队列
1.acquireQueued
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;
failed = false;
return interrupted;
}
// 尝试获取失败后的操作
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()){
interrupted = true;
}
}
} finally {
if (failed){
cancelAcquire(node);
}
}
}
从上面的代码可知,一旦当前线程进入自旋状态,除非成功获取锁,否则将一直自旋下去。再来看看它获取锁失败后的两个操作。
2.shouldParkAfterFailedAcquire
shouldParkAfterFailedAcquire,该方法用于判断 node 前节点的状态,若不是 SIGNAL 状态,则会将其修改成该状态再返回 false。由于 for 循环的缘故,在成功获取锁之前,该方法会一直被调用,直至前节点的状态被修改成 SIGNAL 。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL){
return true;
}
if (ws > 0) {
// 移除 node 前面等待状态为 CANCELLED 的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 修改 node 前节点的等待状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
3.parkAndCheckInterrupt
进入该方法之后线程会进入阻塞。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
acquire 整体过程如下图所示:
acquireInterruptibly
acquireInterruptibly 表示以独占模式获取锁对象,不忽略中断。
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()){
// 抛出异常...
}
if (!tryAcquire(arg)){
doAcquireInterruptibly(arg);
}
}
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;
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()){
// 关键 -> 这里不忽略中断,直接抛出异常中断线程
throw new InterruptedException();
}
}
} finally {
if (failed){
cancelAcquire(node);
}
}
}
tryAcquireNanos
tryAcquireNanos 表示以独占模式获取锁对象,尝试获取失败,则线程进入阻塞状态一段时间。线程在进入阻塞状态后有两种情况:
- 进入阻塞状态,一直到 nanosTimeout 超时,返回 false;
- 进入阻塞状态,时长未到 nanosTimeout 被唤醒,则通过自旋再次尝试,成功获取锁或直到超时返回结果。
下面来看它的实现过程:
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted()){
// 抛出异常...
}
return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}
与上面两个获取锁对象的步骤基本一致,区别在 doAcquireNanos 这个方法:
static final long spinForTimeoutThreshold = 1000L;
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
// 获取当前时间
long lastTime = System.nanoTime();
// 将节点添加进等待队列
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;
failed = false;
return true;
}
if (nanosTimeout <= 0){
return false;
}
if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold){
// 线程进入阻塞状态一段时间
LockSupport.parkNanos(this, nanosTimeout);
}
// 计算尝试剩余时间
long now = System.nanoTime();
nanosTimeout -= now - lastTime;
lastTime = now;
if (Thread.interrupted()){
// 抛出异常
}
}
} finally {
if (failed){
cancelAcquire(node);
}
}
}
release
该操作表示通过独占模式释放锁对象。
public final boolean release(int arg) {
// ①尝试释放锁,空方法留给 AQS 的子类实现
if (tryRelease(arg)) {
Node h = head;
// 判断头节点不为空且节点状态不为 0
if (h != null && h.waitStatus != 0){
// ②关键 ->唤醒同步等待队列中的[头节点的下一个节点]
unparkSuccessor(h);
}
return true;
}
return false;
}
关键来看 unparkSuccessor 这个方法,入参节点一般为等待队列的头节点 head:
private void unparkSuccessor(Node node) {
// 修改节点的等待状态
int ws = node.waitStatus;
if (ws < 0){
compareAndSetWaitStatus(node, ws, 0);
}
// 剔除队列中已经被置为空或者状态为 CANCELLED 的节点
// 通过判断节点的后继节点是否为空,为空则继续往后寻找来实现
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;
}
}
}
// 不为空,则通过 unpark 取消阻塞
if (s != null){
LockSupport.unpark(s.thread);
}
}
需要注意的是 release 释放锁对象后,会唤醒通过 acquire 进入等待队列的线程,然后由于循环的缘故再次尝试获取锁,成功则执行出队操作。