AQS是什么?
AQS是Java中实现阻塞和唤醒线程的最基础类。全称 AbstractQueuedSynchronizer(同步队列),它是一个同步工具也是 Lock 用来实现线程同步的核心组件。而其中也是基于CAS原理比较替换实现添加和移除结点。
从使用层面来说,AQS 的功能分为两种:
-
独占锁:每次只能有一个线程持有锁,比如ReentrantLock就是以独占方式实现的互斥锁。
-
共享锁:允许多个线程同时获取锁,并发访问共享资源,比如ReentrantReadWriteLock读写锁
PS:本文中只讨论独占锁的源码实现
独占锁是什么?
- 当多个线程一起获取锁的时候,只有一个线程能获取到锁,其他线程必须在当前位置阻塞等待。
- 释放锁的功能。获取锁的线程释放锁资源,而且还必须能唤醒正在等待锁资源的一个线程。
首先了解AQS中的重要内部类——Node
在此只列出Node中重点属性。需要了解好这些属性的作用,才能更好理解之后的源码分析。
static final class Node {
// 共享模式的标记
static final Node SHARED = new Node();
// 独占模式的标记
static final Node EXCLUSIVE = null;
// waitStatus变量的值,标志着线程被取消
static final int CANCELLED = 1;
// waitStatus变量的值,标志着后继线程(即队列中此节点之后的节点)需要被阻塞.(用于独占锁)
static final int SIGNAL = -1;
// waitStatus变量的值,标志着线程在Condition条件上等待阻塞.(用于Condition的await等待)
static final int CONDITION = -2;
// waitStatus变量的值,标志着下一个acquireShared方法线程应该被允许。(用于共享锁)
static final int PROPAGATE = -3;
// 标记着当前节点的状态,默认状态是0, 小于0的状态都是有特殊作用,大于0的状态表示已取消
volatile int waitStatus;
// prev和next实现一个双向链表
volatile Node prev;
volatile Node next;
// 该节点拥有的线程
volatile Thread thread;
}
在此进一步说明waitStatus属性:
-
当waitStatus = CANCELLED = 1:
表示Node所代表的当前线程已经取消了排队,即放弃获取锁了。
-
当waitStatus = SIGNAL = -1:
它不是表征当前节点的状态,而是当前节点的下一个节点的状态。
(当一个节点的waitStatus被置为SIGNAL,就说明它的下一个节点(即它的后继节点)已经被挂起了(或者马上就要被挂起了),因此在当前节点释放了锁或者放弃获取锁时,如果它的waitStatus属性为SIGNAL,它还要完成一个额外的操作——唤醒它的后继节点。) -
当waitStatus = CONDITION = -2:
表示这个Node在条件队列中,因为等待某个条件而被阻塞。
-
当waitStatus = PROPAGATE = -3:
使用在共享模式头Node有可能处于这种状态, 表示锁的下一次获取可以无条件传播
AQS就是为了应对线程的阻塞和唤醒,在代码中分别对应acquire()和release()方法。因此接下来的主要内容便是对这两个方法的源码进行讲解分析。
获取锁方法acquire()
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取锁失败,阻塞线程
selfInterrupt();
}
- tryAcquire()方法尝试获取锁:
基于tryAcquire()方法有两种实现,一种是公平锁,另一种是非公平锁。因为Java中默认使用的是非公平锁,因此在本文的源码分析都是基于非公平锁。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires); // 基于非公平锁的尝试获取锁
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // state为0说明该锁没有被线程占用。采用CAS操作对state加1,并设置当前线程为占用锁线程。说明获取锁成功,并返回true
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // state不为0,说明锁已被线程占用。如果占用锁线程为当前线程,则对state加1,并返回true(可重入机制)
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false; // 否则返回false
}
如果获取锁成功返回true,则线程到此结束。如果获取锁失败返回false,则继续往下分析:
- addWaiter()方法添加等待节点进行队列:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); // 基于当前线程和独占锁模式新建结点node
Node pred = tail;
if (pred != null) { // 尾结点不为空,则将node节点加入队列末尾
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) { // 尾结点为空,则初始化头尾节点
if (compareAndSetHead(new Node()))
tail = head;
} else { // 尾结点不为空,则添加node节点进行队列末尾。注意,此处构造了一个空的头结点。
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- acquireQueued()方法采用自旋方式,获取锁:
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结点为头结点,并且尝试获取锁成功
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted; // 自旋直到获取到锁
}
if (shouldParkAfterFailedAcquire(p, node) && // 检查并更新结点状态
parkAndCheckInterrupt()) // 阻塞线程
interrupted = true;
}
} finally {
if (failed)
// failed为true,表示发生异常,非正常退出
// 则将node节点的状态设置成CANCELLED,表示node节点所在线程已取消,不需要唤醒了
cancelAcquire(node);
}
}
- shouldParkAfterFailedAcquire()方法检查更新节点状态,并决定是否需要阻塞当前线程:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) // node节点的前任节点的waitStatus属性为SIGNAL,说明当前节点中包含的线程应该等待。返回true,阻塞线程
return true;
if (ws > 0) { // waitStatus>0说明为CANCEL,则前任节点处于取消状态。
do { // 向前一直找到未取消的节点为止
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 此处waitStatus必为0或者PROPAGATE,不可能是CONDITION状态
// CONDITION(这个是特殊状态,只在condition列表中节点中存在,独占锁队列中不存在这个状态的节点)
// 将前任结点的状态设置成SIGNAL,这样在下一次循环时,就是直接阻塞当前线程
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false; // 返回false,不阻塞线程
}
- parkAndCheckInterrupt()方法阻塞线程:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 阻塞当前线程
return Thread.interrupted(); // 当前线程被唤醒后,返回当前线程中断状态
}
到此,AQS的acquire()方法执行的整个过程分析和源码讲解结束。
释放锁方法release()
public final boolean release(int arg) {
if (tryRelease(arg)) { // 尝试释放锁
Node h = head;
if (h != null && h.waitStatus != 0) // 头结点不为空,而且waitStatus属性不为0。说明很可能存在等待结点需要进行唤醒
unparkSuccessor(h); // 唤醒等待结点
return true;
}
return false;
}
- tryRelease()方法尝试释放锁:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread()) // 占有锁线程不是当前线程,则抛异常
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // state为0,说明当前线程已经没有占有该锁
free = true;
setExclusiveOwnerThread(null); // 设置占有锁线程为null。即该锁自由了,不被任何线程占用
}
setState(c);
return free; // 返回释放锁结果
}
- unparkSuccessor()方法唤醒等待结点:
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next; // 传入的node为空的头结点,因此需要找到后继节点才是真正的等待结点。
if (s == null || s.waitStatus > 0) { // 等待结点为空,或者waitStatus为CANCEL,则从队尾开始往回找,找到waitStatus不为CANCEL的等待结点。即找到未取消的结点为止。
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);
}
到此,AQS的release()方法执行的整个过程分析和源码讲解结束。
最后,AQS的独占锁模式的获取、释放锁的源码讲解分析结束。