一、什么是 AQS?
AbstractQueuedSynchronizer
(简称 AQS)是 Java 并发包 java.util.concurrent.locks
中的一个核心同步框架,用于构建锁和同步器,如:
- ReentrantLock
- ReentrantReadWriteLock
- CountDownLatch
- Semaphore
- FutureTask
AQS 通过一个FIFO 双向等待队列(CLH 队列)管理线程的同步状态,使开发者可以专注于同步逻辑,而不必关注线程的调度、阻塞、唤醒等底层细节。
二、AQS 的核心设计
1. 核心成员变量
private volatile int state; // 同步状态
private transient Node head; // 队列头节点
private transient Node tail; // 队列尾节点
state
:用来表示资源的占用状态(例如是否被锁定、可用信号量数等);head
和tail
:维护一个 CLH 等待队列,用于管理阻塞的线程。
2. 核心方法(模板方法)
AQS 提供一系列模板方法用于子类实现:
// 共享模式
protected int tryAcquireShared(int arg);
protected boolean tryReleaseShared(int arg);// 独占模式
protected boolean tryAcquire(int arg);
protected boolean tryRelease(int arg);
子类需要实现这些方法,以控制对资源的获取与释放逻辑。
三、AQS 工作流程
1. 获取锁(以独占模式为例)
lock.lock() → tryAcquire() 尝试获取 → 获取失败 → 加入 CLH 队列 → park(阻塞)→ 被唤醒时再次尝试
流程:
- 调用
tryAcquire()
尝试获取资源; - 如果失败,则将当前线程封装为
Node
加入等待队列; - 阻塞(park)当前线程;
- 资源释放后,唤醒下一个等待线程。
2. 释放锁
unlock() → tryRelease() 成功 → 唤醒队列中下一个线程
tryRelease()
将state
设置为 0;- 然后调用
unparkSuccessor()
唤醒下一个线程。
四、独占模式 vs 共享模式
模式 | 描述 |
---|---|
独占模式 | 同一时刻只能有一个线程访问,如 ReentrantLock |
共享模式 | 多个线程可以共享资源,如 Semaphore、CountDownLatch |
AQS 区分这两种模式,并分别处理入队、出队、唤醒等逻辑。
五、CLH 队列机制
AQS 使用一种变体的 CLH(Craig–Landin–Hagersten)同步队列 实现线程排队。
队列结构:
head -> Node1 -> Node2 -> ... -> tail
- 每个节点是一个线程的等待快照;
- 新线程失败后加入尾部,前驱释放时唤醒后继;
- 自旋或 park 阻塞等待。
六、典型用法示例
自定义锁的基本写法
class MyLock extends AbstractQueuedSynchronizer {protected boolean tryAcquire(int arg) {return compareAndSetState(0, 1);}protected boolean tryRelease(int arg) {setState(0);return true;}public void lock() {acquire(1); // 内部调用 tryAcquire + 入队逻辑}public void unlock() {release(1);}
}
CountDownLatch 原理
- 使用共享模式;
- 每次
countDown()
执行releaseShared(-1)
; await()
会在 state = 0 前阻塞。
七、AQS 的优点
优点 | 说明 |
---|---|
封装阻塞逻辑 | 使用 LockSupport 封装了 park/unpark |
可复用性强 | 模板方法模式,便于自定义同步器 |
队列高效 | FIFO 队列实现公平性,性能稳定 |
支持两种模式 | 支持共享和独占资源控制 |
八、注意点 & 问题排查
- 死锁:一定要在
try...finally
中释放锁; - state 状态:设计时需合理设置 state 的含义(计数、二进制等);
- CLH 队列泄露:释放时未正确调用
unpark
会导致阻塞线程永久等待; - 性能瓶颈:高并发下需注意锁竞争,考虑使用
StampedLock
或乐观锁优化。
九、AQS 应用类汇总
类名 | 模式 | 简介 |
---|---|---|
ReentrantLock | 独占 | 可重入、可中断、公平/非公平 |
Semaphore | 共享 | 控制资源访问数目 |
CountDownLatch | 共享 | 等待所有任务完成 |
ReentrantReadWriteLock | 共享 + 独占 | 高效读写分离 |
FutureTask | 独占 | 控制异步任务状态 |
AbstractQueuedSynchronizer | 基类 | 框架级同步器 |
十、总结
- AQS 是构建 Java 同步工具的核心;
- 通过队列管理线程阻塞与唤醒;
- 使用模板方法封装了多种模式;
- 掌握 AQS = 掌握 Java 并发的核心原理。
补充:AQS 源码流程图解析 + ReentrantLock 实战源码
一、AQS 获取锁(acquire)源码流程
public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}
流程图解:
acquire()│┌───────┴────────┐↓ ↓tryAcquire() // 自定义尝试获取(成功返回true) (失败)↓ ↓return addWaiter(Node.EXCLUSIVE)↓入队列构造双向链表↓acquireQueued(node, arg)↓while(前驱不是head || 不能获取锁)↓ ↓park() tryAcquire()↓获取成功 → 设置head → unpark下一个
二、AQS 释放锁(release)源码流程
public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;
}
流程图解:
release()│tryRelease()│是否成功释放?↓ ↑true false↓获取 head↓唤醒下一个节点(unpark)
三、ReentrantLock 是怎么用 AQS 实现的?
源码结构简图:
public class ReentrantLock implements Lock {abstract static class Sync extends AbstractQueuedSynchronizer {// 核心逻辑都在此子类中}final Sync sync;
}
ReentrantLock 的两个实现版本:
类型 | 类名 | 特点 |
---|---|---|
非公平锁(默认) | NonfairSync | 直接尝试获取锁,抢占式,性能好 |
公平锁 | FairSync | 排队获取,按顺序,不插队,公平但慢一些 |
tryAcquire 的实现(非公平锁)
protected final boolean tryAcquire(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()) {// 可重入,加重入次数int nextc = c + acquires;setState(nextc);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) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;
}
四、AQS 线程状态控制核心机制:LockSupport
AQS 底层通过 LockSupport.park()
和 unpark(thread)
来挂起/唤醒线程:
park()
:当前线程阻塞,等待被唤醒;unpark(Thread t)
:唤醒指定线程;- 这种控制机制替代了传统的
wait/notify
,更灵活、底层、性能好。
五、总结:如何理解 AQS 的强大之处?
特性 | 说明 |
---|---|
模板方法设计 | 只需实现 tryAcquire 和 tryRelease ,其他线程队列处理由 AQS 自动完成 |
双模式支持 | 支持共享与独占两种访问模型 |
高可复用性 | 可构建多种同步组件(锁、信号量、栅栏等) |
高性能 | 结合 CAS、自旋、队列挂起唤醒机制 |
阻塞线程管理 | 使用 LockSupport 精细控制线程挂起与恢复 |