AQS的理解
目录
- AQS的理解
- 1.什么是AQS
- 2.总体流程
- 3.注意:
AQS的理解
1.什么是AQS
AQS(AbstractQueuedSynchronizer) 是 Java 并发包(JUC)的核心框架,像 ReentrantLock 就是基于它实现的。它内部有两个核心部分:
一是 volatile 的 int 类型 state 变量(比如 ReentrantLock 中用来记录锁的重入次数);
二是 CLH 双向队列,用来存放竞争锁失败的线程,按 FIFO 排队。
当队头线程被唤醒后,看到 state 为 0 就会通过 CAS 改状态抢锁;
如果是非公平锁,新线程可能插队直接改 state,公平锁则严格按队列顺序。
2.总体流程
图示:
一、流程拆解(非公平锁完整逻辑):
我们按流程图一步步走,核心看 “两次抢锁” 何时发生、为何必要:
- 第一次抢锁(最上方的 CAS)
触发条件:线程调用 lock(),直接进入 nonfairTryAcquire(非公平抢锁逻辑)。
操作:尝试用 CAS 将 state 从 0 改为 1(完全插队,不检查队列)。
结果:
成功:直接获取锁(setExclusiveOwnerThread 标记当前线程),流程结束。
失败:进入第二次抢锁。 - 第二次抢锁(currentThread == exclusiveThread 判断)
触发条件:第一次 CAS 失败(state != 0),但可能是 锁重入场景(当前线程已经持有锁)。
操作:判断 currentThread == exclusiveOwnerThread(当前线程是否是锁的持有者):
是:说明是 锁重入,直接执行 setState(state += 1),流程结束。
否:说明锁被其他线程持有,抢锁失败,进入 入队流程。 - 入队流程(addWaiter + acquireQueued)
触发条件:两次抢锁都失败(既没插队成功,也不是锁重入)。
操作:
addWaiter:将当前线程包装成 Node,加入 CLH 队列尾部。
acquireQueued:线程进入队列后,会 循环抢锁(再次调用 tryAcquire),若失败则 park 阻塞,直到被唤醒。
二、“两次抢锁” 的核心设计意图
- 第一次抢锁:极致性能优化
目的:让线程尽可能 “插队” 获取锁,避免进入队列的开销(入队 → 阻塞 → 唤醒的成本很高)。
典型场景:锁刚被释放(state 变为 0)时,新线程可以直接 CAS 抢占,无需等待队列中的线程被唤醒。 - 第二次抢锁:支持 “锁重入”
目的:处理 同一线程多次获取锁 的场景(比如递归调用 lock())。
典型场景:
java
运行
lock.lock();
try {
// 同一线程再次获取锁(重入)
lock.lock();
// …
} finally {
lock.unlock(); // state -= 1
lock.unlock(); // state -= 1 → 最终释放
}
第二次抢锁时,currentThread == exclusiveOwnerThread 为 true,直接增加 state,实现 锁重入。
3. 入队流程:保证公平性兜底
目的:当线程既没插队成功,也不是锁重入时,必须进入队列 排队等待,保证最终能获取锁(遵循 FIFO 原则)。
3.注意:
进入队列阻塞的线程都要排队(FIFO)获取锁,在队头的线程先看到state为0了,它就去改状态值为1(也就是去抢锁),具体来说就是:
进入队列的线程会按 FIFO 排队,队头线程被唤醒后(也就是持有锁的线程执行完逻辑代码调用unlock()方法解锁后),会先检查 state 是否为 0,然后通过 CAS 抢锁(改 state 为 1)。成功则获取锁并出队,失败则重新阻塞。非公平锁可能有新线程插队,公平锁则严格按队列顺序抢锁。