摘要:从一次"线程池卡死导致应用假死"的线上故障出发,通过ReentrantLock的使用案例反推AQS的核心原理。深度剖析AQS的state变量、CLH队列、独占模式与共享模式,配合手写100行代码实现简易版ReentrantLock,揭秘为什么ReentrantLock比synchronized更灵活、公平锁与非公平锁的性能差异、以及Condition条件队列的实现原理。图解入队出队流程,给出并发工具类的最佳实践。
💥 翻车现场
周四下午3点,哈吉米收到告警。
告警:
🚨 应用服务器CPU 0%(假死)
🚨 所有接口超时
🚨 日志没有新输出
🚨 用户反馈:一直加载中
哈吉米:"卧槽,应用假死了?"
紧急重启应用后,查看线程dump:
"pool-1-thread-1" #12 prio=5 waiting on condition
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock
"pool-1-thread-2" #13 prio=5 waiting on condition
java.lang.Thread.State: WAITING (parking)
... (同样在等待锁)
"pool-1-thread-3" #14 prio=5 waiting on condition
... (100个线程都在等待同一把锁)
哈吉米:"所有线程都在等待 AbstractQueuedSynchronizer?这是啥?"
查看代码:
private final ReentrantLock lock = new ReentrantLock();
public void processOrder(Order order) {
lock.lock();
try {
// 处理订单
doProcess(order);
// 调用外部接口(可能很慢)
externalService.notify(order); // 这个接口超时了,一直不返回
} finally {
lock.unlock(); // 永远执行不到这里
}
}
哈吉米:"卧槽,外部接口超时,锁一直没释放,其他线程全部等待!"
晚上,南北绿豆和阿西噶阿西来了。
南北绿豆:"你看到的 AbstractQueuedSynchronizer 就是大名鼎鼎的AQS!"
哈吉米:"AQS是啥?"
阿西噶阿西:"AQS是Java并发工具类的基石,ReentrantLock、CountDownLatch、Semaphore底层都用它实现。"
南北绿豆:"来,我通过ReentrantLock给你讲讲AQS的原理。"
🤔 什么是AQS?
AQS的全称
AbstractQueuedSynchronizer(抽象队列同步器)
南北绿豆:"AQS是JUC(java.util.concurrent)包的核心框架。"
AQS的核心组件
public abstract class AbstractQueuedSynchronizer {
// 1. state变量(同步状态)
private volatile int state;
// 2. CLH队列(等待队列)
private transient volatile Node head; // 队列头
private transient volatile Node tail; // 队列尾
// 3. Node节点(等待线程的封装)
static final class Node {
volatile Thread thread; // 等待的线程
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile int waitStatus; // 等待状态
}
}
三大核心:
| 组件 | 作用 |
|---|---|
| state变量 | 表示同步状态(0=未锁定,1=已锁定) |
| CLH队列 | 存储等待线程的队列(双向链表) |
| Node节点 | 封装等待的线程 |
🎯 通过ReentrantLock理解AQS
ReentrantLock的使用
private final ReentrantLock lock = new ReentrantLock();
public void processOrder(Order order) {
lock.lock(); // 获取锁
try {
// 处理订单(临界区)
doProcess(order);
} finally {
lock.unlock(); // 释放锁
}
}
ReentrantLock的底层实现
public class ReentrantLock implements Lock {
// 内部使用AQS实现
private final Sync sync;
// Sync继承自AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
// ...
}
// 非公平锁
static final class NonfairSync extends Sync {
final void lock() {
// 1. 先尝试CAS抢锁
if (compareAndSetState(0, 1)) {
// 抢到了,设置当前线程为持有者
setExclusiveOwnerThread(Thread.currentThread());
} else {
// 抢不到,进入AQS的等待队列
acquire(1);
}
}
}
}
阿西噶阿西:"看到了吗?ReentrantLock底层用的就是AQS!"
🔍 AQS的加锁流程(独占模式)
场景:3个线程竞争同一把锁
线程A:先到,获取锁 ✅
线程B:后到,进入等待队列
线程C:后到,进入等待队列
完整流程图
sequenceDiagram
participant ThreadA as 线程A
participant State as state变量
participant Queue as CLH队列
participant ThreadB as 线程B
participant ThreadC as 线程C
Note over ThreadA,ThreadC: 初始状态:state=0(未锁定)
ThreadA->>State: 1. CAS(0 → 1)
State->>ThreadA: 成功,获取锁 ✅
ThreadB->>State: 2. CAS(0 → 1)
State->>ThreadB: 失败(state=1)
ThreadB->>Queue: 3. 加入等待队列
Note over Queue: head → [ThreadB] ← tail
ThreadB->>ThreadB: 4. park(阻塞)
ThreadC->>State: 5. CAS(0 → 1)
State->>ThreadC: 失败(state=1)
ThreadC->>Queue: 6. 加入等待队列
Note over Queue: head → [ThreadB] → [ThreadC] ← tail
ThreadC->>ThreadC: 7. park(阻塞)
Note over ThreadA: 执行业务逻辑
ThreadA->>State: 8. unlock: state=1 → 0
ThreadA->>Queue: 9. 唤醒head.next(ThreadB)
Queue->>ThreadB: 10. unpark(唤醒)
ThreadB->>State: 11. CAS(0 → 1)
State->>ThreadB: 成功,获取锁 ✅
Note over ThreadB: 执行业务逻辑
ThreadB->>State: 12. unlock: state=1 → 0
ThreadB->>Queue: 13. 唤醒ThreadC
Queue->>ThreadC: 14. unpark
ThreadC->>State: 15. CAS(0 → 1)
State->>ThreadC: 成功,获取锁 ✅
详细步骤
步骤1:线程A获取锁
// 线程A执行
lock.lock();
// 底层实现
if (compareAndSetState(0, 1)) { // CAS:state从0改成1
// 成功,获取锁
setExclusiveOwnerThread(Thread.currentThread()); // 记录持有者
}
state变化:
state: 0 → 1(已锁定)
持有者:Thread-A
步骤2:线程B竞争锁失败,加入队列
// 线程B执行
lock.lock();
// 底层实现
if (compareAndSetState(0, 1)) { // CAS失败(state=1)
// 失败,加入等待队列
acquire(1);
}
// acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg)) { // 再次尝试获取锁,失败
// 加入等待队列
addWaiter(Node.EXCLUSIVE); // 创建Node节点,加入队列
// 阻塞线程
LockSupport.park(this);
}
}
队列状态:
head → [Node(Thread-B)] ← tail
Node结构:
{
thread: Thread-B,
prev: head,
next: null,
waitStatus: 0
}
步骤3:线程C也加入队列
head → [Node(Thread-B)] → [Node(Thread-C)] ← tail
步骤4:线程A释放锁
// 线程A执行
lock.unlock();
// 底层实现
public void unlock() {
release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) { // state:1 → 0
Node h = head;
if (h != null && h.waitStatus != 0) {
unparkSuccessor(h); // 唤醒head的下一个节点(Thread-B)
}
return true;
}
return false;
}
唤醒流程:
1. state:1 → 0(释放锁)
2. 唤醒head.next(Thread-B)
3. LockSupport.unpark(Thread-B)
4. Thread-B从park()返回,继续执行
5. Thread-B尝试CAS获取锁,成功 ✅
🎯 手写一个简易版ReentrantLock
哈吉米:"理解了流程,能不能自己实现一个?"
南北绿豆:"来,100行代码实现简易版!"
/**
* 简易版ReentrantLock(基于AQS思想)
*/
public class SimpleLock {
// state变量(0=未锁定,1=已锁定)
private volatile int state = 0;
// 持有锁的线程
private Thread exclusiveOwnerThread;
// 等待队列(双向链表)
private final Queue<Thread> waitQueue = new ConcurrentLinkedQueue<>();
/**
* 加锁
*/
public void lock() {
// 尝试CAS获取锁
if (compareAndSetState(0, 1)) {
// 成功,记录持有者
exclusiveOwnerThread = Thread.currentThread();
return;
}
// 失败,加入等待队列
waitQueue.offer(Thread.currentThread());
// 自旋等待(简化版,真实AQS用park)
while (true) {
// 如果队列头是自己,尝试获取锁
if (waitQueue.peek() == Thread.currentThread()) {
if (compareAndSetState(0, 1)) {
// 成功,出队
waitQueue.poll();
exclusiveOwnerThread = Thread.currentThread();
return;
}
}
// 继续等待
LockSupport.parkNanos(1000000); // park 1ms
}
}
/**
* 解锁
*/
public void unlock() {
if (Thread.currentThread() != exclusiveOwnerThread) {
throw new IllegalMonitorStateException("当前线程未持有锁");
}
// 释放锁
exclusiveOwnerThread = null;
state = 0; // volatile写,保证可见性
// 唤醒等待队列的第一个线程
Thread next = waitQueue.peek();
if (next != null) {
LockSupport.unpark(next);
}
}
/**
* CAS操作(简化版)
*/
private boolean compareAndSetState(int expect, int update) {
// 实际用Unsafe.compareAndSwapInt
// 这里简化为synchronized
synchronized (this) {
if (state == expect) {
state = update;
return true;
}
return false;
}
}
}
测试代码
public class SimpleLockTest {
private static final SimpleLock lock = new SimpleLock();
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 100个线程并发执行
CountDownLatch latch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
latch.countDown();
}).start();
}
latch.await();
System.out.println("最终count: " + count);
// 输出:最终count: 100000(正确)
}
}
阿西噶阿西:"看,100行代码就实现了一个基本可用的锁!"
哈吉米:"原来AQS的核心就是:state变量 + 等待队列 + CAS!"
🔍 AQS的两种模式
独占模式(Exclusive)
特点:同一时刻只有一个线程能获取锁。
示例:ReentrantLock
// 独占模式
lock.lock(); // 线程A获取锁
// 线程B、C、D全部等待
lock.unlock(); // 线程A释放,线程B获取
共享模式(Shared)
特点:同一时刻多个线程可以获取锁。
示例:CountDownLatch、Semaphore
// 共享模式(Semaphore,允许3个线程)
Semaphore semaphore = new Semaphore(3);
// 线程A、B、C同时获取 ✅
semaphore.acquire(); // state: 3 → 2 → 1 → 0
// 线程D等待
semaphore.acquire(); // 等待(state=0)
对比
| 模式 | state含义 | 获取锁条件 | 示例 |
|---|---|---|---|
| 独占模式 | 0=未锁定,1=已锁定 | state == 0 | ReentrantLock |
| 共享模式 | 剩余资源数 | state > 0 | Semaphore、CountDownLatch |
🎯 公平锁 vs 非公平锁
非公平锁(默认,性能好⭐⭐⭐⭐⭐)
ReentrantLock lock = new ReentrantLock(); // 默认非公平
// 获取锁流程
public void lock() {
// 1. 先CAS抢锁(不管队列)
if (compareAndSetState(0, 1)) {
// 抢到了,直接获取 ✅
return;
}
// 2. 抢不到,再加入队列
acquire(1);
}
特点:新来的线程可以"插队"抢锁。
时序图:
sequenceDiagram
participant ThreadA as 线程A(持有锁)
participant Queue as 等待队列
participant ThreadB as 线程B(队列头)
participant ThreadC as 线程C(新来的)
Note over Queue: [ThreadB] 在队列等待
ThreadC->>ThreadC: 1. 刚到,尝试CAS
ThreadA->>ThreadA: 2. unlock(释放锁)
par 同时竞争
ThreadB->>ThreadB: 尝试获取锁
ThreadC->>ThreadC: CAS抢锁(插队)
end
ThreadC->>ThreadC: 3. 抢到了 ✅
Note over ThreadB: 继续等待(被插队了)
优点:
- ✅ 性能好(减少线程切换)
- ✅ 吞吐量高
缺点:
- ❌ 可能饿死(队列头的线程一直抢不到)
公平锁(性能差,保证公平⭐⭐⭐)
ReentrantLock lock = new ReentrantLock(true); // 公平锁
// 获取锁流程
public void lock() {
// 1. 检查队列是否有等待的线程
if (hasQueuedPredecessors()) {
// 有等待的线程,不能插队,直接加入队列
acquire(1);
} else {
// 没有等待的线程,尝试CAS
if (compareAndSetState(0, 1)) {
return;
} else {
acquire(1);
}
}
}
特点:严格按FIFO顺序获取锁,不允许插队。
优点:
- ✅ 公平(不会饿死)
缺点:
- ❌ 性能差(需要线程切换)
性能对比
测试:100个线程,每个线程执行1000次加锁
| 锁类型 | 执行时间 | 吞吐量 |
|---|---|---|
| 非公平锁 | 1.2秒 | 83333 ops/s |
| 公平锁 | 3.8秒 | 26315 ops/s |
性能差距:非公平锁快3倍!
南北绿豆:"所以默认用非公平锁,性能更好。"
🎯 ReentrantLock vs synchronized
对比表
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 层级 | JVM层(关键字) | JDK层(类) |
| 锁释放 | 自动释放 | 手动释放(finally) |
| 公平性 | 非公平 | 可选(公平/非公平) |
| 中断响应 | ❌ 不支持 | ✅ 支持(lockInterruptibly) |
| 超时获取 | ❌ 不支持 | ✅ 支持(tryLock) |
| 条件队列 | 1个(wait/notify) | 多个(Condition) |
| 性能 | ⭐⭐⭐⭐(JDK 6+优化后) | ⭐⭐⭐⭐ |
使用场景
用synchronized:
// 简单场景
public synchronized void method() {
// ...
}
// 或者
synchronized (lock) {
// ...
}
用ReentrantLock:
// 需要高级特性
// 1. 可中断
try {
lock.lockInterruptibly(); // 可中断
} catch (InterruptedException e) {
// 处理中断
}
// 2. 超时获取
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 获取到锁
} finally {
lock.unlock();
}
} else {
// 超时,放弃
}
// 3. 多个条件队列
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
🎓 面试标准答案
题目:AQS的核心原理是什么?
答案:
AQS(AbstractQueuedSynchronizer)核心:
三大组件:
- state变量:同步状态(volatile int)
- CLH队列:等待线程的双向链表
- Node节点:封装等待线程
加锁流程:
- CAS尝试将state从0改成1
- 成功 → 获取锁
- 失败 → 加入CLH队列,park阻塞
解锁流程:
- state从1改成0
- 唤醒队列头的下一个线程
- 被唤醒的线程继续竞争锁
两种模式:
- 独占模式:ReentrantLock
- 共享模式:Semaphore、CountDownLatch
题目:ReentrantLock和synchronized的区别?
答案(见对比表):
核心区别:
- synchronized是关键字,ReentrantLock是类
- ReentrantLock支持公平锁、可中断、超时获取
- ReentrantLock支持多个Condition
- synchronized自动释放,ReentrantLock需要手动unlock
选型:
- 简单场景:synchronized
- 需要高级特性:ReentrantLock
🎉 结束语
晚上10点,哈吉米终于搞懂了AQS的原理。
哈吉米:"原来AQS就是:state变量控制状态,CLH队列存等待线程,CAS保证原子性!"
南北绿豆:"对,ReentrantLock、CountDownLatch、Semaphore底层都是AQS,只是对state的定义不同。"
阿西噶阿西:"记住:state表示资源,队列存等待者,CAS保证并发安全。"
哈吉米:"还有公平锁和非公平锁的区别,非公平锁性能好3倍!"
南北绿豆:"对,下次我们讲CountDownLatch和Semaphore,都是基于AQS实现的!"
记忆口诀:
AQS核心三组件,state队列和节点
CAS修改state值,成功获锁失败排
CLH队列双向链,头部唤醒按顺序
独占模式一个占,共享模式多个进
非公平锁性能好,公平锁防饿死
希望这篇文章能帮你轻松掌握AQS的核心原理!理解了AQS,就理解了整个JUC包!💪