一、核心架构:AQS抽象队列同步器
二、AQS核心机制
1. 三大核心组件:
state状态变量:volatile int,表示锁状态(0=未锁定,≥1=锁定/重入次数)
CLH队列:双向链表实现的线程等待队列
Node节点:封装等待线程和状态(包含:CANCELLED/SIGNAL/CONDITION/PROPAGATE)
2. 关键状态值:
// Node状态常量
static final int CANCELLED = 1; // 线程已取消
static final int SIGNAL = -1; // 后继线程需要唤醒
static final int CONDITION = -2; // 在条件队列等待
static final int PROPAGATE = -3; // 共享模式下传播唤醒
三、ReentrantLock工作流程
1. 获取锁(lock()):
2. 释放锁(unlock()):
四、公平锁 vs 非公平锁实现差异
1. 非公平锁(默认):
final boolean nonfairTryAcquire(int acquires) {// 直接尝试获取锁(可能插队)if (c == 0 && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}// 检查重入else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;setState(nextc);return true;}return false;
}
2. 公平锁:
protected final boolean tryAcquire(int acquires) {// 先检查队列是否有等待线程if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}// 重入逻辑相同
}
五、AQS关键方法解析
方法 | 作用 |
---|---|
tryAcquire(int) | 尝试获取锁(需子类实现) |
tryRelease(int) | 尝试释放锁(需子类实现) |
acquireQueued() | 线程加入队列后自旋获取锁 |
shouldParkAfterFailedAcquire() | 检查是否应该阻塞线程 |
unparkSuccessor() | 唤醒后继节点线程 |
compareAndSetState() | CAS更新state值(保证原子性) |
六、问题总结
Q:ReentrantLock如何基于AQS实现?
A:
ReentrantLock的核心实现依赖于AQS框架:
状态管理:
使用AQS的
state
变量记录锁状态(0=未锁定,≥1=重入次数)通过
compareAndSetState()
保证原子更新
线程排队:
获取锁失败的线程被封装为Node加入CLH队列
队列基于双向链表实现(FIFO)
锁获取流程:
先尝试
tryAcquire()
直接获取锁失败后调用
addWaiter()
加入队列尾部在队列中自旋检查前驱节点状态
最终通过
LockSupport.park()
阻塞线程
锁释放流程:
调用
tryRelease()
减少重入计数当state归零时,调用
unparkSuccessor()
唤醒队首线程被唤醒线程重新尝试获取锁
公平性实现:
公平锁:先检查队列是否有等待线程(
hasQueuedPredecessors()
)非公平锁:允许插队直接尝试获取锁
Q:AQS为什么使用CLH队列?
A:
CLH队列(Craig, Landin, and Hagersten锁)的优势:
无锁入队:通过CAS操作实现线程安全入队
低竞争:每个线程只监控前驱节点状态
高效唤醒:只需修改前驱节点的状态即可唤醒后继线程
适应性:完美支持超时、中断等复杂场景
Q:ReentrantLock如何基于AQS实现?
A:
ReentrantLock的核心实现依赖于AQS框架,未获取锁的线程会被完全阻塞:
阻塞时机:当线程尝试获取锁失败,且自旋检查后仍无法获取时
阻塞方式:
调用
LockSupport.park()
方法使线程进入WAITING状态线程释放CPU资源,不再消耗计算周期
线程状态变为
WAITING (parking)
阻塞位置:
线程在CLH队列中排队等待
每个线程监控前驱节点的状态
唤醒机制:
前驱节点释放锁时调用
unparkSuccessor()
通过
LockSupport.unpark()
精确唤醒后继线程唤醒后线程重新尝试获取锁
与忙等待的区别:
不同于忙等待(while循环),park()会使线程让出CPU
避免空转消耗CPU资源
通过操作系统级同步原语实现高效阻塞/唤醒
七、进阶问题问题
state
变量为什么用volatile?保证多线程间的可见性
确保锁状态变化能被立即感知
配合CAS实现无锁状态更新
如何处理锁重入?
// 获取锁时检查当前线程是否是持有者 if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;setState(nextc); // 增加重入计数return true; }
为什么唤醒后继节点而不是所有线程?
减少不必要的线程唤醒(惊群效应)
保证公平性(FIFO顺序)
提高系统吞吐量
AQS如何支持超时机制?
public final boolean tryAcquireNanos(int arg, long nanosTimeout) {if (Thread.interrupted()) throw new InterruptedException();return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); // 超时获取 }
在自旋过程中检查超时时间
超时后标记节点为CANCELLED
为什么非公平锁性能更高?
减少线程切换开销(新线程可直接抢锁)
避免唤醒延迟(CLH队列唤醒需要时间)
但可能导致线程饥饿
总结:AQS设计精髓
模板方法模式:定义骨架流程(acquire/release),子类实现关键操作(tryAcquire/tryRelease)
无锁算法:通过CAS实现安全的状态更新
等待队列:优雅管理阻塞线程
可扩展性:支持独占/共享两种模式