一、AQS是个啥?为啥这么重要?🤔
AQS = AbstractQueuedSynchronizer(抽象队列同步器)
想象一下,你要去苹果店🍎买最新款iPhone。店门口排着长队,只有一个门,一次只能进一个人。这就是AQS的工作原理!
- 门 = 共享资源(锁)
- 排队的人 = 等待线程
- 队列 = CLH队列
- 保安 = AQS控制器
🎯 为什么AQS这么牛?
Java并发包(J.U.C)里的大量工具都是基于AQS实现的:
markdown
AQS(框架层)
├── ReentrantLock(独占锁)🔒
├── ReentrantReadWriteLock(读写锁)📖
├── Semaphore(信号量)🚦
├── CountDownLatch(倒计时门栓)⏱️
├── CyclicBarrier(循环栅栏)🚧
└── ThreadPoolExecutor.Worker(线程池)🏊
一句话总结:
Doug Lea大神创造了AQS这个模板框架,让你只需要实现几个方法,就能自定义各种同步器!就像给你一个"万能钥匙模具"🔑,你只需要稍微调整就能开各种锁!
二、AQS核心数据结构 📊
1️⃣ 状态变量 state
java
// AQS的核心:一个int类型的状态变量
private volatile int state;
// 为啥用volatile?
// ✅ 保证可见性:一个线程修改,其他线程立即看到
// ✅ 禁止指令重排序
state的含义取决于具体实现:
同步器 | state含义 | 举例 |
---|---|---|
ReentrantLock | 0=未锁定,1=已锁定,>1=重入次数 | state=3表示锁被重入了3次 |
Semaphore | 可用许可数量 | state=5表示还有5个许可 |
CountDownLatch | 倒计时数量 | state=3表示还需要3次countDown |
ReentrantReadWriteLock | 高16位=读锁数量,低16位=写锁数量 | state=65537表示1个读锁 |
2️⃣ CLH队列(核心!)
CLH = Craig, Landin, and Hagersten三位大神的名字首字母
scss
AQS内部的等待队列
Head Tail
↓ ↓
┌────┐ ┌────┐ ┌────┐ ┌────┐
→ │Node│ ←→ │Node│ ←→ │Node│ ←→ │Node│
└────┘ └────┘ └────┘ └────┘
│ │ │ │
Thread1 Thread2 Thread3 Thread4
(持锁) (等待) (等待) (等待)
双向链表结构:
java
static final class Node {
// 前驱节点
volatile Node prev;
// 后继节点
volatile Node next;
// 当前线程
volatile Thread thread;
// 等待状态
volatile int waitStatus;
// 下一个等待条件队列的节点(Condition用)
Node nextWaiter;
}
3️⃣ Node的等待状态 waitStatus
java
// 等待状态的几种取值
static final int CANCELLED = 1; // 取消状态(超时/中断)
static final int SIGNAL = -1; // 后继节点需要被唤醒
static final int CONDITION = -2; // 在条件队列中等待
static final int PROPAGATE = -3; // 共享模式下的传播
static final int INITIAL = 0; // 初始状态
生活比喻:
scss
🏪 苹果店门口排队买iPhone
SIGNAL(-1): "嘿,后面的兄弟,我买完了会叫你!"
CANCELLED(1): "算了,不买了,我走了!"
CONDITION(-2): "等通知说有货了我再来!"
PROPAGATE(-3): "有货了!大家一起进!"
INITIAL(0): 刚来,还没想好
三、AQS两种模式 🎭
独占模式(Exclusive)🔒
一次只有一个线程能获取资源,就像单人厕所🚻,只能一个人用!
java
// ReentrantLock就是独占模式
ReentrantLock lock = new ReentrantLock();
lock.lock(); // 占厕所
try {
// 上厕所中...💩
} finally {
lock.unlock(); // 出来,下一位!
}
共享模式(Shared)👥
多个线程可以同时获取资源,就像电影院🎬,多个座位可以同时坐人!
java
// Semaphore就是共享模式(5个座位)
Semaphore semaphore = new Semaphore(5);
semaphore.acquire(); // 占一个座位
try {
// 看电影中...🍿
} finally {
semaphore.release(); // 释放座位
}
四、AQS工作流程(独占模式)🎬
场景:三个线程抢一个锁
java
// 三个线程同时执行
Thread-1: lock.lock();
Thread-2: lock.lock();
Thread-3: lock.lock();
📽️ 电影开始
第1帧:Thread-1首先到达
ini
1. 调用 tryAcquire(1) 尝试获取锁
2. state从0变成1,成功!✅
3. 设置 exclusiveOwnerThread = Thread-1
4. Thread-1愉快地执行临界区代码 🎉
当前状态:
state = 1
exclusiveOwnerThread = Thread-1
队列:空
第2帧:Thread-2到达,发现锁被占
scss
1. 调用 tryAcquire(1)
2. 发现state=1,获取失败 ❌
3. 调用 addWaiter() 加入等待队列
4. 创建Node节点,放入队尾
5. 调用 acquireQueued() 开始等待
6. 自旋检查前驱节点是否是head
7. 发现不是,调用 LockSupport.park() 挂起 😴
当前状态:
Head Tail
↓ ↓
┌────┐ ┌────┐
│Node│ ←→ │Node│
└────┘ └────┘
│ │
(空节点) Thread-2
(SIGNAL)
第3帧:Thread-3到达
scss
同样的流程,加入队尾
Head Tail
↓ ↓
┌────┐ ┌────┐ ┌────┐
│Node│ ←→ │Node│ ←→ │Node│
└────┘ └────┘ └────┘
│ │ │
(空节点) Thread-2 Thread-3
(SIGNAL) (SIGNAL)
第4帧:Thread-1释放锁
scss
1. Thread-1调用 unlock()
2. 调用 tryRelease(1)
3. state从1变成0
4. exclusiveOwnerThread = null
5. 调用 unparkSuccessor() 唤醒后继节点
6. LockSupport.unpark(Thread-2) 唤醒Thread-2 ⏰
当前状态:
state = 0
exclusiveOwnerThread = null
第5帧:Thread-2被唤醒
markdown
1. Thread-2从park()中醒来 😊
2. 再次调用 tryAcquire(1)
3. 成功获取锁!state=1
4. 设置自己为head节点
5. 移除旧head节点
6. Thread-2开始执行临界区代码
当前状态:
Head Tail
↓ ↓
┌────┐ ┌────┐
│Node│ ←→ │Node│
└────┘ └────┘
│ │
Thread-2 Thread-3
(持锁) (等待)
五、核心源码解析 💻
1️⃣ 获取锁流程(acquire)
java
public final void acquire(int arg) {
// 三个步骤:
// 1. 尝试获取锁
if (!tryAcquire(arg) &&
// 2. 获取失败,加入队列
acquireQueued(
// 3. 创建节点并加入队尾
addWaiter(Node.EXCLUSIVE), arg)) {
// 如果被中断,设置中断标志
selfInterrupt();
}
}
完整流程图:
scss
开始
│
├─→ tryAcquire(1) ─→ 成功? ─→ Yes ─→ 结束(拿到锁)✅
│ │
│ No
│ ↓
├─→ addWaiter() ─→ 创建Node ─→ 加入队尾
│ ↓
└─→ acquireQueued() ─→ 自旋等待
│ │
│ ├─→ 前驱是head? ─→ Yes ─→ tryAcquire ─→ 成功? ─→ 结束✅
│ │ │
│ │ No
│ No │
│ │ ↓
│ ↓ park() 😴
│ park() 😴 │
│ │ │
│ 被唤醒 ⏰ ←──────────────────────────────────┘
│ │
└─────────────────→┘(循环)
2️⃣ 释放锁流程(release)
java
public final boolean release(int arg) {
// 1. 尝试释放锁
if (tryRelease(arg)) {
Node h = head;
// 2. 如果head不为空且状态不是初始态
if (h != null && h.waitStatus != 0)
// 3. 唤醒后继节点
unparkSuccessor(h);
return true;
}
return false;
}
// 唤醒后继节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
// CAS设置为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); // 唤醒!⏰
}
六、Condition条件队列 🎪
除了CLH同步队列,AQS还支持条件队列(配合Lock使用):
java
ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition(); // 队列未满条件
Condition notEmpty = lock.newCondition(); // 队列非空条件
// 生产者
lock.lock();
try {
while (queue.isFull()) {
notFull.await(); // 等待"未满"条件
}
queue.add(item);
notEmpty.signal(); // 通知"非空"条件
} finally {
lock.unlock();
}
// 消费者
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 等待"非空"条件
}
queue.take();
notFull.signal(); // 通知"未满"条件
} finally {
lock.unlock();
}
Condition原理:
yaml
同步队列(CLH队列)
┌────┐ ┌────┐ ┌────┐
│Node│ ←→ │Node│ ←→ │Node│
└────┘ └────┘ └────┘
条件队列(单向链表)
notFull: ┌────┐ → ┌────┐ → null
│Node│ │Node│
└────┘ └────┘
notEmpty: ┌────┐ → null
│Node│
└────┘
七、手写一个简单的AQS应用 ✍️
java
// 自定义一个独占锁(类似ReentrantLock)
public class MyLock {
// 继承AQS
private static class Sync extends AbstractQueuedSynchronizer {
// 尝试获取锁
@Override
protected boolean tryAcquire(int arg) {
// CAS设置state从0到1
if (compareAndSetState(0, 1)) {
// 设置当前线程为独占线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 尝试释放锁
@Override
protected boolean tryRelease(int arg) {
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
// 清空独占线程
setExclusiveOwnerThread(null);
// 释放state
setState(0);
return true;
}
// 是否持有锁
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
}
private final Sync sync = new Sync();
// 加锁
public void lock() {
sync.acquire(1);
}
// 解锁
public void unlock() {
sync.release(1);
}
}
// 使用
MyLock lock = new MyLock();
lock.lock();
try {
// 临界区代码
System.out.println("拿到锁了!");
} finally {
lock.unlock();
}
八、面试应答模板 🎤
面试官:说说AQS的核心原理和数据结构?
你的回答:
AQS是AbstractQueuedSynchronizer的缩写,是Java并发包的基础框架,ReentrantLock、Semaphore等都基于它实现。
核心数据结构有三个:
1️⃣ state变量(volatile int):表示同步状态
- 在ReentrantLock中表示锁的重入次数
- 在Semaphore中表示可用许可数
- 通过CAS操作保证原子性
2️⃣ CLH队列(双向链表):存储等待线程
- 每个节点包含:thread、waitStatus、prev、next
- waitStatus有SIGNAL、CANCELLED、CONDITION等状态
- head节点是空节点或持锁线程,tail是队尾
3️⃣ 独占线程(exclusiveOwnerThread):记录当前持锁线程
工作原理:
- 线程尝试获取资源(tryAcquire),成功则执行
- 失败则创建Node节点加入CLH队列尾部
- 在队列中自旋检查前驱是否是head,是则尝试获取
- 否则调用LockSupport.park()挂起等待
- 持锁线程释放资源时,唤醒后继节点
- 被唤醒的线程继续尝试获取资源
支持两种模式:
- 独占模式(Exclusive):如ReentrantLock
- 共享模式(Shared):如Semaphore、CountDownLatch
九、总结图示 🎨
java
🏗️ AQS架构全景图
┌─────────────────────────────┐
│ AbstractQueuedSynchronizer │
│ │
│ ┌────────────────────────┐ │
│ │ volatile int state │ │ ← 核心状态
│ └────────────────────────┘ │
│ │
│ ┌────────────────────────┐ │
│ │ CLH Queue (双向链表) │ │
│ │ │ │
│ │ Head → Node ↔ Node │ │ ← 等待队列
│ │ ↓ │ │
│ │ Tail │ │
│ └────────────────────────┘ │
└─────────────────────────────┘
↑
│ 继承
┌───────────────┴───────────────┐
│ │
ReentrantLock.Sync Semaphore.Sync
(独占模式) (共享模式)
记忆口诀:
AQS是个大管家,
state状态它来管,
CLH队列排排站,
独占共享两模式,
CAS操作保安全,
park和unpark来睡眠,
并发工具都靠它!🎵
核心要点:
- ✅ AQS是J.U.C的基石
- ✅ state + CLH队列 + CAS操作
- ✅ 独占模式 vs 共享模式
- ✅ park/unpark控制线程挂起唤醒
- ✅ 模板方法设计模式
Doug Lea大神说:
"给我一个AQS,我能撬动整个并发世界!"🌍