前言
在Java并发编程的世界里,AQS(AbstractQueuedSynchronizer)是一个里程碑式的存在。它是java.util.concurrent包的基石,ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier等并发工具都是基于AQS构建的。
面试中,AQS是区分"会用并发工具"和"理解并发原理"的分水岭。
本文将深入剖析AQS的核心设计思想,从CLH队列到state状态,再到ReentrantLock的公平与非公平锁实现,带你彻底理解这个并发框架的底层原理。
一、AQS概述:并发框架的基石
1.1 什么是AQS?
AQS(AbstractQueuedSynchronizer)是Java并发包(JUC)中的一个抽象类,它提供了一个基于FIFO队列的同步器框架,可以用来构建锁或其他同步组件。
设计思想:
- 使用一个
volatile int state表示同步状态 - 通过内置的FIFO队列(CLH队列变种)管理线程的等待队列
- 提供独占模式和共享模式两种资源获取方式
1.2 AQS的核心三要素
| 要素 | 作用 | 实现方式 |
|---|---|---|
| state | 同步状态 | volatile int state,通过CAS操作修改 |
| CLH队列 | 线程等待队列 | FIFO双向链表,管理等待线程 |
| CAS操作 | 原子性保证 | Unsafe类的CAS方法 |
1.3 AQS的两种模式
| 模式 | 含义 | 典型实现 |
|---|---|---|
| 独占模式(Exclusive) | 同一时刻只有一个线程能获取资源 | ReentrantLock |
| 共享模式(Shared) | 多个线程可以同时获取资源 | CountDownLatch、Semaphore |
1.4 AQS的核心方法
AQS采用了模板方法模式,子类通过重写以下方法来定制同步逻辑:
java
public abstract class AbstractQueuedSynchronizer {
// 需要子类重写的方法
protected boolean tryAcquire(int arg) // 独占式尝试获取资源
protected boolean tryRelease(int arg) // 独占式尝试释放资源
protected int tryAcquireShared(int arg) // 共享式尝试获取资源
protected boolean tryReleaseShared(int arg) // 共享式尝试释放资源
protected boolean isHeldExclusively() // 判断是否被当前线程独占
// 模板方法(final,子类不能重写)
public final void acquire(int arg) // 获取资源入口
public final boolean release(int arg) // 释放资源入口
public final void acquireShared(int arg) // 共享获取入口
public final boolean releaseShared(int arg) // 共享释放入口
}
二、AQS核心实现原理
2.1 state 状态管理
state是AQS的核心状态变量,用volatile修饰保证可见性:
java
public abstract class AbstractQueuedSynchronizer {
// 同步状态,用 volatile 保证可见性
private volatile int state;
// 获取state
protected final int getState() {
return state;
}
// 设置state(普通写,无原子性保证)
protected final void setState(int newState) {
state = newState;
}
// CAS设置state(原子操作)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
}
state的含义由子类定义:
ReentrantLock:state = 0 表示未锁定,state > 0 表示锁定次数(可重入计数)CountDownLatch:state 表示需要等待的线程数(计数器)Semaphore:state 表示剩余许可数量
2.2 CLH队列:线程等待队列
AQS使用CLH锁队列的变种作为线程等待队列。
CLH队列的核心结构
java
static final class Node {
// 节点状态
volatile int waitStatus;
// 等待状态常量
static final int CANCELLED = 1; // 线程已取消
static final int SIGNAL = -1; // 后继线程需要被唤醒
static final int CONDITION = -2; // 线程在条件队列中等待
static final int PROPAGATE = -3; // 共享模式下传播唤醒
// 前驱节点
volatile Node prev;
// 后继节点
volatile Node next;
// 当前节点对应的线程
volatile Thread thread;
// 条件队列的下一个节点
Node nextWaiter;
}
队列结构图
┌─────────────────────────────────────────────────────────┐
│ AQS │
│ ┌─────────────────────────────────────────────────┐ │
│ │ CLH Queue │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │ Head │◄──►│ Node │◄──►│ Node │◄──►│ Tail │ │ │
│ │ │(dummy)│ │(thread)│ │(thread)│ │(thread)│ │ │
│ │ └──────┘ └──────┘ └──────┘ └──────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ state = 0/1/N │
└─────────────────────────────────────────────────────────┘
关键特点:
- FIFO双向链表:保证线程获取资源的公平性
- 虚拟头节点:Head节点是一个占位节点,不关联具体线程
- 自旋+CAS:通过自旋+CAS保证入队和出队的线程安全
2.3 独占模式获取与释放流程
acquire() 获取资源
java
public final void acquire(int arg) {
// 1. tryAcquire:尝试获取资源(子类实现)
// 2. addWaiter:如果失败,将当前线程加入等待队列
// 3. acquireQueued:在队列中自旋等待获取资源
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter() 入队操作
java
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 快速尝试:如果队列不为空,CAS将节点加入队尾
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 队列为空或CAS失败,进入完整入队逻辑
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;
}
}
}
}
acquireQueued() 等待获取
java
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);
}
}
release() 释放资源
java
public final boolean release(int arg) {
// 尝试释放资源(子类实现)
if (tryRelease(arg)) {
Node h = head;
// 唤醒后继线程
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
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;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒线程
}
三、ReentrantLock 的 AQS 实现
ReentrantLock是AQS在独占模式下的典型实现。它的内部定义了Sync抽象类继承AQS,并提供了公平锁(FairSync)和非公平锁(NonfairSync)两种实现。
3.1 整体架构
java
public class ReentrantLock implements Lock {
private final Sync sync;
// 同步器抽象类
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
// 非公平尝试获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 未锁定时,CAS尝试获取
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// 可重入:state增加
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// 释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
// 非公平锁
static final class NonfairSync extends Sync {
final void lock() {
// 上来先CAS抢一次
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
// 公平锁
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
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;
}
}
}
3.2 非公平锁实现详解
非公平锁的核心:新来的线程有机会"插队"获取锁,而不必排队等待。
java
static final class NonfairSync extends Sync {
final void lock() {
// 关键点1:刚进来就直接CAS尝试获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 失败才进入AQS队列
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
// Sync中的nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 关键点2:再次尝试CAS获取
if (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;
}
非公平锁的"插队"体现在:
lock()方法一进来就CAS抢锁,不检查队列tryAcquire中再次CAS抢锁,不检查队列- 只有抢锁失败后,才会乖乖进入队列排队
3.3 公平锁实现详解
公平锁的核心:严格按照FIFO顺序获取锁,不允许插队。
java
static final class FairSync extends Sync {
final void lock() {
// 直接调用acquire,没有提前CAS
acquire(1);
}
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;
}
}
// 检查是否有线程排队
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 队列不为空,且头节点的后继节点不是当前线程
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
公平锁与非公平锁的核心区别:
| 步骤 | 非公平锁 | 公平锁 |
|---|---|---|
| lock() | 先CAS抢锁一次 | 直接acquire |
| tryAcquire | 直接CAS抢锁 | 先检查是否有排队线程 |
| 插队 | 允许 | 不允许 |
3.4 公平锁 vs 非公平锁:性能与场景
| 特性 | 非公平锁 | 公平锁 |
|---|---|---|
| 吞吐量 | 更高 | 较低 |
| 线程饥饿 | 可能发生 | 不会发生 |
| 上下文切换 | 较少 | 较多 |
| 适用场景 | 高并发、追求吞吐量 | 避免饥饿、需要公平性 |
为什么非公平锁性能更高?
- 减少线程挂起和唤醒的开销
- 刚释放锁的线程有CPU缓存亲和性,立即获取锁效率高
非公平锁的潜在问题:
- 可能导致线程饥饿(长期等待的线程一直获取不到锁)
- 但实际场景中,这种概率很低,且可重入锁的特性会缓解饥饿
四、AQS的共享模式:CountDownLatch示例
为了全面理解AQS,我们简要看一个共享模式的实现:CountDownLatch。
java
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
// 共享模式尝试获取
protected int tryAcquireShared(int acquires) {
// state为0表示可以获取
return (getState() == 0) ? 1 : -1;
}
// 共享模式尝试释放
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
sync.releaseShared(1);
}
}
共享模式与独占模式的区别:
- 独占模式:state表示资源是否被占用(0/1或可重入计数)
- 共享模式:state表示可用资源数量(可被多个线程同时获取)
五、AQS的高级特性
5.1 条件队列(Condition)
AQS通过ConditionObject内部类实现条件队列,配合await()和signal()实现线程的精确唤醒。
java
public class ConditionObject implements Condition {
private Node firstWaiter; // 条件队列头
private Node lastWaiter; // 条件队列尾
public final void await() throws InterruptedException {
// 将当前线程加入条件队列
Node node = addConditionWaiter();
// 释放锁
int savedState = fullyRelease(node);
// 阻塞等待
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
}
// 重新获取锁
acquireQueued(node, savedState);
}
public final void signal() {
// 将条件队列中的节点转移到同步队列
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
}
条件队列与同步队列的关系:
同步队列(CLH) 条件队列
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│Node1│◄──►│Node2│ │NodeA│◄──►│NodeB│
└─────┘ └─────┘ └─────┘ └─────┘
↑ ↑ ↑ ↑
等待锁 等待锁 await等待 await等待
signal() 操作:将条件队列的节点转移到同步队列
5.2 中断响应
AQS提供了可中断的获取方法:
java
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
5.3 超时获取
AQS支持超时获取锁:
java
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}
六、常见面试题解析
Q1:AQS的核心思想是什么?
答 :AQS的核心思想是将同步状态(state)与等待队列(CLH队列)相结合。通过CAS操作修改state,线程获取资源失败时进入FIFO队列等待,释放资源时唤醒队列中的后继线程。这种设计将复杂的同步逻辑抽象为"尝试获取-排队等待-唤醒"的通用框架。
Q2:CLH队列为什么是双向链表?
答:双向链表的设计主要考虑:
- 高效的前驱访问:节点需要获取前驱节点的状态(waitStatus)
- 取消节点的清理:当节点被取消时,需要从队列中移除,双向链表便于操作
- 从尾部向前查找 :
unparkSuccessor需要从尾部向前查找第一个有效节点
Q3:公平锁和非公平锁的区别是什么?
答:
- 公平锁:获取锁之前先检查等待队列中是否有线程在排队,如果有则排队等待,严格按FIFO顺序获取
- 非公平锁:获取锁时直接CAS尝试,不管队列中是否有等待线程,允许插队
性能上:非公平锁吞吐量更高,但可能导致线程饥饿;公平锁公平性好,但上下文切换开销大。
Q4:ReentrantLock的可重入性是如何实现的?
答 :通过state状态变量实现。state表示锁的持有次数:
- 第一次获取锁:
state = 1 - 同一个线程再次获取:
state + 1 - 释放锁:
state - 1 - 当
state = 0时,锁完全释放,其他线程可以获取
Q5:AQS为什么使用int类型的state,而不是boolean?
答:
- 支持可重入锁:需要计数功能
- 支持共享模式:如Semaphore需要表示剩余许可数量
- 扩展性:子类可以根据需要定义state的含义
七、总结
AQS核心架构图
┌─────────────────────────────────────────────────────────────────┐
│ AbstractQueuedSynchronizer │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ volatile int state │ │
│ │ (同步状态,由子类定义含义) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ CLH Queue (FIFO) │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │ Head │◄──►│ Node │◄──►│ Node │◄──►│ Tail │ │ │
│ │ │(dummy)│ │thread│ │thread│ │thread│ │ │
│ │ └──────┘ └──────┘ └──────┘ └──────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 模板方法 │ │
│ │ acquire() release() acquireShared() releaseShared() │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 子类实现 │ │
│ │ tryAcquire() tryRelease() tryAcquireShared() │ │
│ │ tryReleaseShared() isHeldExclusively() │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ReentrantLock│ │CountDownLatch│ │ Semaphore │ │
│ │ (独占模式) │ │ (共享模式) │ │ (共享模式) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
关键要点回顾
| 知识点 | 核心内容 |
|---|---|
| state状态 | volatile int state,通过CAS修改,子类自定义含义 |
| CLH队列 | FIFO双向链表,管理等待线程 |
| 独占模式 | 同一时刻只有一个线程能获取资源 |
| 共享模式 | 多个线程可以同时获取资源 |
| 公平锁 | 检查队列,不允许插队 |
| 非公平锁 | 直接CAS,允许插队 |
| 可重入性 | state计数实现 |
| 条件队列 | ConditionObject实现精确唤醒 |
学习建议
- 源码阅读 :建议跟着本文的源码片段,自己动手追踪一遍AQS的
acquire和release流程 - 调试实践:写一个简单的多线程程序,断点调试观察CLH队列的变化
- 对比学习 :对比公平锁和非公平锁的
tryAcquire实现差异
AQS是理解Java并发包的钥匙,掌握它,你就掌握了JUC的灵魂。
📌 系列回顾:
下一篇预告:JUC并发容器深度解析------ConcurrentHashMap、CopyOnWriteArrayList源码分析
希望这篇文章能帮助你在面试中轻松应对AQS相关的问题!如有疑问,欢迎在评论区交流讨论。