深入分析Java中的AQS:从应用到原理的思维链条
AQS(AbstractQueuedSynchronizer)是Java并发编程中的核心框架,它为众多同步工具提供了基础支持。让我通过链路追踪的方式,从应用场景逐步深入探究其工作原理。
1. 从应用场景出发
在日常开发中,我们常用的锁和同步工具包括:
- ReentrantLock:可重入锁,替代synchronized
- CountDownLatch:倒计时门闩,等待一组线程完成
- Semaphore:信号量,控制同时访问的线程数
- ReentrantReadWriteLock:读写锁,读共享写互斥
- CyclicBarrier:循环栅栏,等待一组线程达到某个点
这些工具虽然功能各异,但它们的内部实现都基于AQS。
2. AQS的核心设计思想
AQS提供了一个框架,通过模板方法模式实现了同步器的骨架,具体同步工具只需实现少量方法即可。
核心设计要点:
- 状态管理 :维护一个
volatile int state
表示同步状态 - 队列管理:通过一个FIFO队列管理等待线程
- 阻塞原语 :使用
LockSupport.park()/unpark()
实现线程阻塞/唤醒
3. AQS工作机制链路追踪
以ReentrantLock为例,让我们追踪其获取锁和释放锁的完整流程:
3.1 获取锁流程
scss
lock() → acquire(1) → tryAcquire(1) →
成功 → 获取锁成功并返回
失败 → addWaiter(Node.EXCLUSIVE) → enq(node) → acquireQueued(node, 1) →
parkAndCheckInterrupt() → LockSupport.park(this)
- 调用
lock()
方法 lock()
内部调用AQS的acquire(1)
acquire(1)
先调用tryAcquire(1)
尝试获取锁- 如果成功,直接返回
- 如果失败,继续下一步
- 调用
addWaiter(Node.EXCLUSIVE)
创建独占模式的节点 - 通过
enq(node)
将节点加入队列 - 调用
acquireQueued(node, 1)
使线程阻塞等待 - 阻塞是通过
parkAndCheckInterrupt()
实现的 - 内部调用
LockSupport.park(this)
让线程暂停
3.2 释放锁流程
scss
unlock() → release(1) → tryRelease(1) →
成功 → unparkSuccessor(h) → LockSupport.unpark(s.thread)
- 调用
unlock()
方法 unlock()
内部调用AQS的release(1)
release(1)
调用tryRelease(1)
尝试释放锁- 如果释放成功,调用
unparkSuccessor(h)
唤醒后继节点 - 唤醒是通过
LockSupport.unpark(s.thread)
实现的
4. AQS核心源码剖析
4.1 状态管理
java
private volatile int state; // 同步状态
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
state
是AQS的核心字段,不同工具对其有不同解释- 在ReentrantLock中,表示锁被同一线程重入的次数
- 在Semaphore中,表示剩余许可数量
- 在CountDownLatch中,表示剩余计数
4.2 队列管理
java
private transient volatile Node head; // 队列头节点
private transient volatile Node tail; // 队列尾节点
// Node节点定义
static final class Node {
volatile int waitStatus; // 等待状态
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile Thread thread; // 关联的线程
Node nextWaiter; // 下一个等待者
// 等待状态常量
static final int CANCELLED = 1; // 取消状态
static final int SIGNAL = -1; // 信号状态(后继节点需要唤醒)
static final int CONDITION = -2; // 条件等待
static final int PROPAGATE = -3; // 共享模式传播
}
- AQS通过一个双向链表实现的FIFO队列来管理等待线程
- 每个节点对应一个线程,以及该线程的等待状态
waitStatus
的不同值表示节点的不同状态
4.3 阻塞与唤醒
java
// 线程阻塞(停车)
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
// 唤醒后继节点
private void unparkSuccessor(Node node) {
// 如果状态为负,尝试将其置为0
int ws = node.waitStatus;
if (ws < 0) compareAndSetWaitStatus(node, ws, 0);
// 找到需要唤醒的后继节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾部向前查找最前面的一个状态<=0的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0) s = t;
}
// 唤醒后继节点
if (s != null) LockSupport.unpark(s.thread);
}
- AQS使用
LockSupport
的park/unpark机制实现线程的阻塞和唤醒 - 这比wait/notify更灵活,可以精确控制要唤醒的线程
5. AQS的两种模式
AQS支持两种同步模式,不同的工具使用不同模式:
5.1 独占模式(Exclusive)
- 同一时刻只有一个线程能获取资源
- 实现类需要覆盖的方法:
tryAcquire(int)
tryRelease(int)
- 典型实现:ReentrantLock
5.2 共享模式(Shared)
- 同一时刻可以有多个线程获取资源
- 实现类需要覆盖的方法:
tryAcquireShared(int)
tryReleaseShared(int)
- 典型实现:CountDownLatch、Semaphore、ReadLock
6. 记忆与理解的思维链条
为了更好地记忆和理解AQS,可以建立以下思维链条:
- 应用层:各种同步工具(ReentrantLock, CountDownLatch等)
- 框架层:AQS框架(模板方法设计模式)
- 状态控制:volatile int state(CAS操作)
- 队列管理:双向FIFO队列(Node节点链表)
- 线程控制:park/unpark机制(LockSupport工具类)
- 模式选择:独占模式vs共享模式
7. AQS的精华总结
- 设计精妙:通过简单的状态变量和队列,实现了复杂的同步语义
- 可扩展性:模板方法设计模式,允许不同工具定制不同行为
- 高性能:使用CAS操作避免锁的开销
- 易用性:屏蔽了并发编程的复杂性,提供简单统一的接口
通过这种链路式的分析,我们可以看到AQS是如何从一个简单的抽象框架,支撑起Java中各种丰富多样的同步工具。理解AQS,就掌握了Java并发编程的核心基石。