1. 为什么需要深入理解AQS
在Java并发编程领域,AbstractQueuedSynchronizer(AQS)堪称并发工具类的基石。我第一次接触AQS是在调试一个高并发场景下的死锁问题时,发现单纯使用synchronized已经无法满足性能需求。AQS作为Java.util.concurrent包的核心框架,支撑了CountDownLatch、ReentrantLock、Semaphore等常用并发工具的实现。
重要提示:理解AQS不仅是为了应付面试,更是解决实际高并发问题的关键。我在电商秒杀系统开发中就曾因对AQS理解不透彻,导致自定义同步器出现性能瓶颈。
2. AQS核心原理剖析
2.1 同步队列模型
AQS内部维护着一个FIFO的CLH队列(Craig, Landin, and Hagersten lock queue的变种),这个设计源自于上世纪90年代的论文研究。队列中的每个节点都保存着线程引用和等待状态:
java复制static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}
我在分析线程阻塞过程时发现,当线程获取锁失败时,会经历以下步骤:
- 创建Node节点并加入队列尾部
- 通过自旋方式尝试获取锁
- 最终通过LockSupport.park()挂起线程
2.2 状态管理机制
AQS使用一个volatile修饰的int型state变量来表示同步状态。这个设计非常精妙:
- ReentrantLock中state表示重入次数
- Semaphore中state表示剩余许可数
- CountDownLatch中state表示剩余计数
java复制private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
实际经验:state的修改必须通过CAS操作,我在实现自定义同步器时曾直接调用setState()导致线程安全问题。
3. AQS关键方法实现
3.1 独占式同步实现
以ReentrantLock为例,其非公平锁的实现展示了典型的独占式同步:
java复制final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
关键点分析:
- CAS设置state保证原子性
- 处理重入场景
- 非公平性体现在不检查队列直接尝试获取
3.2 共享式同步实现
Semaphore的实现展示了共享式同步的特点:
java复制protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
与独占式的区别:
- 允许多个线程同时获取
- 返回值表示剩余资源量
- 采用自旋+CAS的无锁编程模式
4. AQS实战应用技巧
4.1 自定义同步器开发
基于AQS实现一个简单的门闩:
java复制class SimpleLatch {
private final Sync sync = new Sync();
private static class Sync extends AbstractQueuedSynchronizer {
protected int tryAcquireShared(int acquires) {
return (getState() == 1) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
setState(1);
return true;
}
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void release() {
sync.releaseShared(1);
}
}
开发注意事项:
- 根据需求选择继承独占式或共享式
- 正确实现tryAcquire/tryRelease方法
- 考虑中断和超时需求
4.2 性能优化实践
在高并发场景下,AQS的性能调优要点:
- 减少CAS竞争:通过分组锁设计降低争用
- 合理设置自旋次数:避免CPU空转与上下文切换的平衡
- 使用条件队列优化:ConditionObject的正确使用
我在订单系统优化中通过以下配置提升了30%吞吐量:
- 将单一锁拆分为16个分段锁
- 设置最大自旋次数为CPU核心数*2
- 使用单独的Condition处理不同业务状态
5. 典型问题排查实录
5.1 线程饥饿问题
现象:某些线程长时间获取不到锁
排查步骤:
- 检查同步器实现是否公平
- 分析队列长度变化
- 使用JStack查看线程状态
解决方案:
- 改用公平锁模式
- 引入tryAcquire超时机制
- 优化锁粒度
5.2 死锁场景分析
常见死锁模式:
- 重入锁未正确释放
- 多个锁获取顺序不一致
- 条件变量使用不当
调试技巧:
bash复制# 使用jstack检测死锁
jstack -l <pid> | grep -A 10 deadlock
6. AQS高级特性解析
6.1 条件变量实现
ConditionObject是AQS的重要组件,其实现要点:
- 维护独立的条件队列
- await()时释放锁并进入等待
- signal()时转移节点到同步队列
java复制public class ConditionObject implements Condition {
private transient Node firstWaiter;
private transient Node lastWaiter;
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
// ...
}
}
6.2 响应中断设计
AQS对中断的处理非常精细:
- acquireInterruptibly()方法提供可中断获取
- 中断时会取消节点并抛出InterruptedException
- 中断状态会通过Node节点传播
实际案例:我在实现交易系统时,通过正确处理中断实现了优雅的停止机制。
