CAS与AQS 底层原理、核心流程、面试最全详解
CAS 是无锁并发基石(原子操作、乐观锁);AQS 是 JUC 并发包核心骨架(锁、同步器底层父类)。AQS 底层依赖 CAS 实现加锁,二者是「基础工具 → 上层框架」的依赖关系,是Java并发面试核心重点。
一、CAS 原理(Compare And Swap)
1. 核心定义
CAS(比较并交换):是一种无锁、乐观锁、原子操作。不阻塞线程,通过自旋重试保证并发安全。
核心逻辑三段式(原子执行,不可拆分):
- 获取旧值:读取内存当前值 V
- 比较校验:判断 V 是否等于预期值 E
- 交换更新:相等则将值更新为新值 N;不相等说明被修改,更新失败,重试
公式:CAS(V,E,N)
2. 底层实现
- Java 层:
Unsafe类的compareAndSwapXXX本地方法 - JVM 层:汇编指令 cmpxchg
- 硬件层:CPU 总线锁/缓存锁保证硬件级原子性
核心优势:无阻塞、无线程切换、用户态自旋、并发性能远超synchronized重量级锁。
3. CAS 三大致命问题(面试必考)
(1)ABA 问题
场景:线程1读取值A,线程2先改成B、再改回A,线程1CAS比较成功,误以为数据未被修改,导致逻辑异常。
解决方案:版本号机制(AtomicStampedReference),不仅比较值,还比较版本号。
(2)循环自旋开销大
竞争极度激烈时,CAS持续失败、无限自旋,空转消耗CPU,导致CPU飙升。
解决方案:自适应自旋、超时CAS、退避策略。
(3)只能保证单个变量原子性
CAS 仅能原子更新一个变量,无法保证多变量复合操作的原子性。
解决方案:使用锁 / AtomicReference 封装对象。
4. CAS 应用场景
- 所有 Atomic 原子类(AtomicInteger、AtomicLong)
- AQS 锁状态更新、线程入队、抢锁
- 乐观锁、计数器、限流统计
二、AQS 原理(AbstractQueuedSynchronizer)
AQS 是 JUC 所有锁和同步工具的底层骨架。
ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier、ThreadPoolExecutor 核心同步逻辑全部基于 AQS 实现。
1. AQS 三大核心组件
(1)state 同步状态(核心变量)
volatile int state,通过 CAS 修改,代表锁/资源状态:
- ReentrantLock:0=无锁,1=加锁,>1=可重入次数
- CountDownLatch:代表剩余倒数次数
- Semaphore:代表剩余可用信号量数量
(2)CLH 双向阻塞队列
抢锁失败的线程,会封装成 Node 节点,进入队列阻塞排队,避免CAS无限自旋浪费CPU。
结构:head 头节点 + 无数 Node + tail 尾节点
(3)Node 节点
封装阻塞线程、等待状态、前后指针:
- thread:阻塞的线程
- waitStatus:节点等待状态(阻塞、取消、唤醒)
- prev / next:双向链表指针
2. AQS 核心工作模式
AQS 只定义排队、阻塞、唤醒、状态更新通用逻辑,不定义具体锁逻辑。
子类(ReentrantLock等)重写:tryAcquire、tryRelease 实现自己的加锁/释放规则。
3. AQS 完整抢锁流程(面试必背)
- 尝试CAS抢锁:线程通过CAS修改state,成功则直接获取锁,执行业务
- 抢锁失败入队:CAS失败,封装为Node节点,CAS入队
- 前置检查:判断前驱节点是否有效,是否需要阻塞
- LockSupport.park() 阻塞:挂起线程,释放CPU,不再自旋
- 解锁唤醒后继:持有锁线程释放锁,修改state,唤醒队列中下一个线程
- 后继线程竞争锁:被唤醒线程再次CAS尝试抢锁,循环执行
4. AQS 两种工作模式
(1)独占模式(Exclusive)
同一时刻只允许一个线程获取锁。
代表:ReentrantLock
(2)共享模式(Share)
允许多个线程同时获取资源。
代表:CountDownLatch、Semaphore、读写锁读模式
5. AQS 为什么性能高?
- 无锁抢锁:成功时直接CAS,不阻塞,速度极快
- 失败才阻塞:抢不到才park,避免空转浪费CPU
- 队列公平调度:有序排队、精准唤醒,无无效竞争
- 细粒度控制:自旋+阻塞结合,兼顾高并发与低开销
三、CAS 与 AQS 核心关系(灵魂考点)
- CAS是AQS的底层原子工具:AQS所有state修改、节点入队全部靠CAS实现线程安全
- CAS只负责更新,AQS负责调度:CAS抢锁失败会无限空转,AQS封装队列+阻塞,解决CAS CPU空转问题
- 互补关系:CAS负责无锁修改,AQS负责失败排队、线程阻塞唤醒
四、面试极简背诵版(直接默写)
CAS 背诵
CAS是比较并交换的硬件级原子乐观锁,通过预期值比对实现无锁更新,性能高;存在ABA问题、CPU自旋空转、仅支持单变量原子操作三大缺陷,ABA可通过版本号解决。
AQS 背诵
AQS是JUC并发工具底层骨架,核心由state状态、CLH双向队列、Node节点组成。通过CAS尝试抢锁,失败线程入队阻塞,锁释放后唤醒后继线程,实现高效的线程同步,分为独占和共享两种模式。
五、高频面试追问
1. AQS 和 synchronized 区别?
- synchronized:JVM底层实现,自动加锁释放,阻塞重量级、灵活性低
- AQS(Lock):Java代码实现,可中断、可超时、可公平/非公平、可读写分离,性能更优、灵活度更高
2. 为什么 AQS 要用 CAS?
保证多线程并发修改 state、入队操作的原子性,不使用重量级锁,保证高并发性能。
3. CAS 自旋和 AQS park 区别?
- CAS自旋:用户态循环重试,不释放CPU,高并发耗CPU
- park阻塞:线程挂起,释放CPU,内核态阻塞,无资源消耗
六、ReentrantLock 基于 AQS 完整加锁/解锁流程(面试核心)
ReentrantLock 是 AQS 独占模式的经典实现,核心特性:可重入、可公平/非公平、可中断、可超时。内部通过自定义同步器 FairSync(公平锁)、NonFairSync(非公平锁)重写 AQS 的 tryAcquire/tryRelease 方法,实现加锁释放逻辑。
1. 核心前置认知
- AQS state=0:无锁状态,线程可竞争锁
- AQS state>0:已加锁,数值代表可重入次数
- 独占模式:同一时刻仅一个线程持有锁
- 非公平锁:上来直接抢锁,不排队,吞吐量高(默认)
- 公平锁:先判断队列是否有等待线程,有序竞争,无线程饥饿
2. 非公平锁 完整加锁流程(默认)
核心特点:不看排队队列,直接CAS抢占锁,效率更高
- 初始CAS抢锁:线程调用 lock(),直接通过 CAS 尝试将 state 从 0 修改为 1。
修改成功 → 直接获取锁,设置当前线程为独占持有线程,执行业务逻辑。 - 抢锁失败,进入 tryAcquire:CAS 失败说明锁被占用,执行非公平锁的 tryAcquire 逻辑。
- 判断可重入:判断当前持有锁的线程是否是自身:
是自身:state +1,可重入成功,直接返回true - 不是自身:返回加锁失败
- 失败入队阻塞:加锁失败,执行 AQS 核心逻辑:将当前线程封装为 Node 节点,通过 CAS 加入 CLH 阻塞队列。
- 自旋校验+park阻塞:线程循环判断前驱节点是否为头节点,尝试再次抢锁;多次失败后调用 LockSupport.park() 挂起线程,释放CPU,等待唤醒。
3. 公平锁 完整加锁流程
核心特点:严格遵循先来先服务,杜绝插队,解决线程饥饿
- 禁止直接插队:线程调用 lock(),不会直接CAS抢锁,优先判断 AQS 等待队列是否存在等待线程。
- 队列有线程:直接放弃抢锁,进入队列排队阻塞。
- 队列无线程:尝试CAS修改state抢锁。
- 可重入校验:抢锁成功则持有锁;失败则判断是否为当前线程重入,是则state累加,否则入队阻塞。
4. 解锁完整流程(tryRelease)
ReentrantLock 解锁必须层层递减重入次数,并非一次解锁直接释放
- 校验持有线程:解锁线程必须是锁的独占持有线程,否则抛出异常。
- 重入次数递减:state 自减 1。
- 判断是否完全释放:
递减后 state > 0:代表多次重入,锁未释放,仅返回false - 递减后 state = 0:锁完全释放,清空独占持有线程
- 唤醒后继线程:锁完全释放后,遍历CLH队列,唤醒head节点的下一个有效等待线程。
- 后继竞争锁:被唤醒的线程再次通过CAS尝试获取锁,循环执行AQS抢锁逻辑。
5. 非公平锁 vs 公平锁 核心区别(面试高频)
- 非公平锁(默认):上来直接抢锁,允许插队,吞吐量高、性能好,可能导致尾部线程饥饿,生产默认使用。
- 公平锁:严格排队、无饥饿,线程切换开销大,吞吐量低,适用于对公平性有强要求的业务。
6. ReentrantLock 可重入原理总结
AQS 的 state 变量记录重入次数,同一线程每次加锁state+1,每次解锁state-1,只有 state 归0才会真正释放锁,完美支持方法嵌套加锁,避免自身死锁。
7. 面试终极背诵版
ReentrantLock基于AQS独占模式实现,默认非公平锁,加锁时先CAS抢占state,成功则持有锁;失败则判断可重入,重入成功累加state,否则线程封装Node入队阻塞。解锁时递减state,归0则释放锁并唤醒队列后继线程。公平锁会优先判断等待队列,禁止插队,保证线程公平竞争。