最近面试被虐得很惨,被问到 AQS(AbstractQueuedSynchronizer)原理时,我支支吾吾答不上来。回来后痛定思痛,花了整整三天时间深入研究,终于搞懂了这个 Java 并发编程的核心组件。这篇博客就分享我的学习成果,希望能帮你彻底掌握 AQS。
一、为什么需要 AQS?
先看一个简单的多线程场景:多个线程同时操作一个共享变量,如何保证线程安全?
public class Counter {
private int count = 0;
public void increment() {
count++; // 非原子操作,线程不安全
}
}
我们可以用synchronized关键字:
public synchronized void increment() {
count++;
}
或者用AtomicInteger:
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子操作
}
这两种方式背后其中的synchronized 就是依赖于 AQS。AQS 是 Java 并发包的基础框架,许多同步工具类如ReentrantLock、CountDownLatch、Semaphore等都是基于 AQS 实现的。理解 AQS 原理,是掌握 Java 并发编程的关键。
二、AQS 核心思想:CLH 队列锁
AQS 的核心思想是用一个 FIFO 队列来管理线程:
- 当多个线程竞争同一个锁时,未获取到锁的线程会被放入队列
- 队列中的线程会按照先来后到的顺序等待获取锁
- 当锁被释放时,队列中的第一个线程会被唤醒并尝试获取锁
这个队列基于 CLH(Craig, Landin, and Hagersten)锁实现,是一种基于链表的自旋锁。
2.1 队列结构
AQS 内部维护了一个双向链表,每个节点是一个Node对象:
static final class Node {
// 共享模式标记
static final Node SHARED = new Node();
// 独占模式标记
static final Node EXCLUSIVE = null;
// 线程状态:初始为0,还有CANCELLED(1)、SIGNAL(-1)等
volatile int waitStatus;
// 前驱节点
volatile Node prev;
// 后继节点
volatile Node next;
// 等待的线程
volatile Thread thread;
// 链接到等待条件的下一个节点
Node nextWaiter;
}
2.2 两种模式
AQS 支持两种锁模式:
- 独占模式:同一时间只有一个线程能获取锁,如
ReentrantLock - 共享模式:多个线程可以同时获取锁,如
Semaphore、CountDownLatch
节点通过nextWaiter字段来标记是共享模式还是独占模式。
三、AQS 核心方法与状态
3.1 核心状态变量
AQS 的核心是一个volatile修饰的整数state,表示同步状态:
private volatile int state;
state的含义由具体子类定义,例如在ReentrantLock中,state表示锁的重入次数- 通过 CAS(Compare and Swap)操作来原子性地修改
state的值
3.2 核心方法
AQS 提供了以下几个关键方法,需要子类去实现:
- tryAcquire(int arg):尝试以独占模式获取锁
- tryRelease(int arg):尝试以独占模式释放锁
- tryAcquireShared(int arg):尝试以共享模式获取锁
- tryReleaseShared(int arg):尝试以共享模式释放锁
- isHeldExclusively():判断当前锁是否由当前线程独占
这些方法的实现需要依赖state变量,通过 CAS 操作来修改state。
3.3 模板方法
AQS 通过模板方法模式定义了一套获取锁和释放锁的框架:
// 独占模式获取锁
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 独占模式释放锁
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 共享模式获取锁
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
// 共享模式释放锁
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
子类只需要实现前面提到的几个方法,就可以轻松实现一个自定义的同步器。
四、源码解析:以 ReentrantLock 为例
4.1 公平锁与非公平锁
ReentrantLock有公平锁和非公平锁两种实现,我们先看非公平锁的实现:
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1)) // 尝试直接获取锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 调用AQS的acquire方法
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
非公平锁会先尝试直接获取锁,如果失败才会进入队列。
4.2 公平锁实现
static final class FairSync extends Sync {
final void lock() {
acquire(1); // 直接调用AQS的acquire方法
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 公平锁会先检查队列中是否有前驱节点
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
公平锁会先检查队列中是否有前驱节点,只有没有前驱节点时才会尝试获取锁。
4.3 加锁流程
当调用lock()方法时,整个加锁流程如下:
- 尝试直接获取锁(非公平锁)
- 如果失败,调用 AQS 的
acquire()方法 acquire()方法会先调用tryAcquire()方法(由子类实现)- 如果
tryAcquire()失败,将当前线程封装成Node节点加入队列 - 线程进入阻塞状态,等待被唤醒
- 当锁被释放时,队列中的第一个线程会被唤醒并尝试获取锁
4.4 释放锁流程
当调用unlock()方法时:
- 调用 AQS 的
release()方法 release()方法会先调用tryRelease()方法(由子类实现)- 如果
tryRelease()成功,唤醒队列中的下一个线程 - 被唤醒的线程尝试获取锁
五、AQS 应用场景
AQS 是 Java 并发包的核心,许多同步工具类都是基于 AQS 实现的:
- ReentrantLock:可重入锁,支持公平和非公平模式
- CountDownLatch:倒计时锁,多个线程等待,直到计数器为 0
- Semaphore:信号量,控制同时访问的线程数量
- CyclicBarrier:循环屏障,多个线程相互等待,直到所有线程都到达
- ReentrantReadWriteLock:读写锁,允许多个线程同时读,但写时互斥
理解 AQS 原理,有助于我们更好地使用这些工具类,也能帮助我们自定义同步器。
六、面试常见问题
-
AQS 的核心思想是什么?
- AQS 的核心思想是用一个 FIFO 队列来管理线程,用一个
volatile变量state来表示同步状态 - 线程竞争锁失败时会被放入队列,等待锁释放时被唤醒
- AQS 的核心思想是用一个 FIFO 队列来管理线程,用一个
-
AQS 支持哪两种锁模式?
- 独占模式(如 ReentrantLock)
- 共享模式(如 Semaphore、CountDownLatch)
-
ReentrantLock 的公平锁和非公平锁有什么区别?
- 非公平锁会先尝试直接获取锁,不管队列中是否有前驱节点
- 公平锁会先检查队列中是否有前驱节点,只有没有前驱节点时才会尝试获取锁
-
AQS 的主要方法有哪些?
- 核心方法:
tryAcquire()、tryRelease()、tryAcquireShared()、tryReleaseShared()、isHeldExclusively() - 模板方法:
acquire()、release()、acquireShared()、releaseShared()
- 核心方法:
-
为什么说 AQS 是 Java 并发包的基础?
- 许多同步工具类都是基于 AQS 实现的,理解 AQS 有助于我们更好地使用这些工具类
- 掌握 AQS 原理,我们还可以自定义同步器,满足特定的业务需求
七、AQS 的核心数据结构
AQS 的核心是一个双向链表和一个状态变量:
private transient volatile Node head; // 队列头节点
private transient volatile Node tail; // 队列尾节点
private volatile int state; // 同步状态
7.1 Node 节点结构
static final class Node {
// 共享模式标记
static final Node SHARED = new Node();
// 独占模式标记
static final Node EXCLUSIVE = null;
// 线程状态:初始为0,还有CANCELLED(1)、SIGNAL(-1)、CONDITION(-2)、PROPAGATE(-3)
volatile int waitStatus;
// 前驱节点
volatile Node prev;
// 后继节点
volatile Node next;
// 等待的线程
volatile Thread thread;
// 链接到等待条件的下一个节点,或标记共享模式
Node nextWaiter;
// 判断是否为共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}
// 获取前驱节点,确保不为null
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
7.2 状态变量
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) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
八、独占模式核心方法
8.1 acquire (int arg) 方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这个方法的逻辑是:
- 尝试获取锁(调用
tryAcquire) - 如果获取失败,将当前线程加入队列(
addWaiter) - 进入队列后,进入自旋状态尝试获取锁(
acquireQueued) - 如果在等待过程中被中断,会调用
selfInterrupt重新设置中断状态
8.2 tryAcquire (int arg) 方法
这个方法需要子类实现,用于尝试获取锁:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
8.3 addWaiter (Node mode) 方法
将当前线程封装成 Node 节点并加入队列尾部:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 尝试快速插入尾部
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 快速插入失败,使用enq方法
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.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
8.4 acquireQueued (final Node node, int arg) 方法
节点进入队列后,会进入自旋状态尝试获取锁:
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;
}
// 检查是否需要挂起当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
8.5 shouldParkAfterFailedAcquire (Node pred, Node node) 方法
检查获取锁失败后是否应该挂起当前线程:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 前驱节点状态为SIGNAL,表示当前节点可以安全挂起
return true;
if (ws > 0) {
// 前驱节点状态为CANCELLED,跳过所有CANCELLED的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 其他状态,将前驱节点状态设为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
8.6 release (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;
}
8.7 tryRelease (int arg) 方法
需要子类实现,尝试释放锁:
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
8.8 unparkSuccessor (Node node) 方法
唤醒后继节点:
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾部向前查找最前面的一个处于非CANCELLED状态的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒线程
}
九、共享模式核心方法
9.1 acquireShared (int arg) 方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
9.2 tryAcquireShared (int arg) 方法
需要子类实现,尝试以共享模式获取锁:
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
9.3 doAcquireShared (int arg) 方法
private void doAcquireShared(int arg) {
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);
}
}
9.4 releaseShared (int arg) 方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
9.5 tryReleaseShared (int arg) 方法
需要子类实现,尝试以共享模式释放锁:
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
十、条件变量实现
AQS 通过ConditionObject类实现了条件变量:
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
private transient Node firstWaiter; // 条件队列的第一个节点
private transient Node lastWaiter; // 条件队列的最后一个节点
// 构造方法
public ConditionObject() { }
// 其他方法...
}
10.1 await () 方法
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
10.2 signal () 方法
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
10.3 signalAll () 方法
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
十一、总结
通过深入分析 AQS 的原理和源码,我们可以看到:
- AQS 的核心是一个 FIFO 队列和一个状态变量,通过 CAS 操作实现原子性
- 独占模式和共享模式的实现逻辑基本类似,但在细节上有所不同
- 条件变量通过一个单独的条件队列实现,与同步队列相互协作
- AQS 提供了一套模板方法,子类只需要实现几个关键方法就可以实现自定义同步器
理解 AQS 的核心原理和源码,对于我们理解 Java 并发包中的各种同步工具类(如 ReentrantLock、CountDownLatch 等)有很大帮助。同时,也能帮助我们在面试中更好地回答 AQS 相关的问题。
希望这篇博客能帮助你彻底掌握 AQS。如果有任何疑问,欢迎在评论区留言讨论!
242





