CAS与AQS 底层原理、核心流程、面试最全详解

约 4791 字

CAS与AQS 底层原理、核心流程、面试最全详解

CAS 是无锁并发基石(原子操作、乐观锁);AQS 是 JUC 并发包核心骨架(锁、同步器底层父类)。AQS 底层依赖 CAS 实现加锁,二者是「基础工具 → 上层框架」的依赖关系,是Java并发面试核心重点。

一、CAS 原理(Compare And Swap)

1. 核心定义

CAS(比较并交换):是一种无锁、乐观锁、原子操作。不阻塞线程,通过自旋重试保证并发安全。

核心逻辑三段式(原子执行,不可拆分):

  1. 获取旧值:读取内存当前值 V
  2. 比较校验:判断 V 是否等于预期值 E
  3. 交换更新:相等则将值更新为新值 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 完整抢锁流程(面试必背)

  1. 尝试CAS抢锁:线程通过CAS修改state,成功则直接获取锁,执行业务
  2. 抢锁失败入队:CAS失败,封装为Node节点,CAS入队
  3. 前置检查:判断前驱节点是否有效,是否需要阻塞
  4. LockSupport.park() 阻塞:挂起线程,释放CPU,不再自旋
  5. 解锁唤醒后继:持有锁线程释放锁,修改state,唤醒队列中下一个线程
  6. 后继线程竞争锁:被唤醒线程再次CAS尝试抢锁,循环执行

4. AQS 两种工作模式

(1)独占模式(Exclusive)

同一时刻只允许一个线程获取锁。

代表:ReentrantLock

(2)共享模式(Share)

允许多个线程同时获取资源。

代表:CountDownLatch、Semaphore、读写锁读模式

5. AQS 为什么性能高?

  • 无锁抢锁:成功时直接CAS,不阻塞,速度极快
  • 失败才阻塞:抢不到才park,避免空转浪费CPU
  • 队列公平调度:有序排队、精准唤醒,无无效竞争
  • 细粒度控制:自旋+阻塞结合,兼顾高并发与低开销

三、CAS 与 AQS 核心关系(灵魂考点)

  1. CAS是AQS的底层原子工具:AQS所有state修改、节点入队全部靠CAS实现线程安全
  2. CAS只负责更新,AQS负责调度:CAS抢锁失败会无限空转,AQS封装队列+阻塞,解决CAS CPU空转问题
  3. 互补关系: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抢占锁,效率更高

  1. 初始CAS抢锁:线程调用 lock(),直接通过 CAS 尝试将 state 从 0 修改为 1。
    修改成功 → 直接获取锁,设置当前线程为独占持有线程,执行业务逻辑。
  2. 抢锁失败,进入 tryAcquire:CAS 失败说明锁被占用,执行非公平锁的 tryAcquire 逻辑。
  3. 判断可重入:判断当前持有锁的线程是否是自身:
    是自身:state +1,可重入成功,直接返回true
  4. 不是自身:返回加锁失败
  5. 失败入队阻塞:加锁失败,执行 AQS 核心逻辑:将当前线程封装为 Node 节点,通过 CAS 加入 CLH 阻塞队列。
  6. 自旋校验+park阻塞:线程循环判断前驱节点是否为头节点,尝试再次抢锁;多次失败后调用 LockSupport.park() 挂起线程,释放CPU,等待唤醒。

3. 公平锁 完整加锁流程

核心特点:严格遵循先来先服务,杜绝插队,解决线程饥饿

  1. 禁止直接插队:线程调用 lock(),不会直接CAS抢锁,优先判断 AQS 等待队列是否存在等待线程。
  2. 队列有线程:直接放弃抢锁,进入队列排队阻塞。
  3. 队列无线程:尝试CAS修改state抢锁。
  4. 可重入校验:抢锁成功则持有锁;失败则判断是否为当前线程重入,是则state累加,否则入队阻塞。

4. 解锁完整流程(tryRelease)

ReentrantLock 解锁必须层层递减重入次数,并非一次解锁直接释放

  1. 校验持有线程:解锁线程必须是锁的独占持有线程,否则抛出异常。
  2. 重入次数递减:state 自减 1。
  3. 判断是否完全释放:
    递减后 state > 0:代表多次重入,锁未释放,仅返回false
  4. 递减后 state = 0:锁完全释放,清空独占持有线程
  5. 唤醒后继线程:锁完全释放后,遍历CLH队列,唤醒head节点的下一个有效等待线程。
  6. 后继竞争锁:被唤醒的线程再次通过CAS尝试获取锁,循环执行AQS抢锁逻辑。

5. 非公平锁 vs 公平锁 核心区别(面试高频)

  • 非公平锁(默认):上来直接抢锁,允许插队,吞吐量高、性能好,可能导致尾部线程饥饿,生产默认使用。
  • 公平锁:严格排队、无饥饿,线程切换开销大,吞吐量低,适用于对公平性有强要求的业务。

6. ReentrantLock 可重入原理总结

AQS 的 state 变量记录重入次数,同一线程每次加锁state+1,每次解锁state-1,只有 state 归0才会真正释放锁,完美支持方法嵌套加锁,避免自身死锁。

7. 面试终极背诵版

ReentrantLock基于AQS独占模式实现,默认非公平锁,加锁时先CAS抢占state,成功则持有锁;失败则判断可重入,重入成功累加state,否则线程封装Node入队阻塞。解锁时递减state,归0则释放锁并唤醒队列后继线程。公平锁会优先判断等待队列,禁止插队,保证线程公平竞争。