Java 并发:CAS + AQS 超通俗详解(原理、场景、代码、技术栈、对比)
一、先搞懂两个核心基础概念(通俗版)
1. CAS:无锁乐观锁(Compare And Swap,比较并交换)
通俗理解
不使用操作系统内核锁,纯 CPU 硬件指令实现并发修改,全程用户态,无上下文切换,性能极高。
操作三步原子完成(CPU 保证不可拆分):
- 拿到内存旧值
V - 预期值
E(线程本地缓存的值) - 新值
N
- 如果
V == E:没人修改过,直接更新为N,返回成功 - 如果
V != E:中间被其他线程改了,更新失败,自旋重试
优点:无内核锁阻塞、吞吐量高
缺点:自旋空耗 CPU、ABA 问题、只能保证单个变量原子操作
底层依赖
CPU 指令:
cmpxchg(x86 架构),Java 通过Unsafe类调用本地方法封装 CAS 操作。2. AQS:抽象队列同步器(AbstractQueuedSynchronizer)
通俗理解
锁的底层框架模板,JDK 所有锁(ReentrantLock/CountDownLatch/Semaphore 等)底层全部基于 AQS 实现。
核心三要素:
- state 同步状态(int 变量):核心标记,CAS 修改 state 实现加锁 / 释放
- 独占锁:state=0 无锁,state=1 持有锁,>1 代表可重入次数
- 共享锁:state 代表许可数 / 剩余等待线程数
- 双向 FIFO 等待队列(CLH 队列):抢锁失败的线程封装成 Node 节点排队,阻塞挂起
- 独占 / 共享两套模式:独占(排他锁,ReentrantLock)、共享(读写锁、CountDownLatch)
本质:AQS 底层大量依赖 CAS修改 state、队列头节点,CAS 是 AQS 的底层基石!
关系总结:AQS = 并发锁框架,CAS = AQS 实现同步的底层原子工具
二、CAS 完整详解
1. CAS 应用场景
场景 1:原子计数器(AtomicInteger/AtomicLong)
适合:简单数字自增、统计、并发计数,不需要复杂锁逻辑
场景 2:无锁并发容器(ConcurrentHashMap 元素更新)
JDK8 ConcurrentHashMap 节点值更新、size 统计底层全是 CAS
场景 3:自旋锁手动实现(自定义乐观锁)
场景 4:AQS 底层修改 state、队列头节点(AQS 离不开 CAS)
场景 5:原子引用、解决 ABA(AtomicStampedReference)
2. CAS 核心技术栈
- 底层硬件:CPU
cmpxchg原子指令 - Java 底层类:
sun.misc.Unsafe(提供 native CAS 方法) - JDK 工具包:
java.util.concurrent.atomic全系列原子类 - 上层框架:ConcurrentHashMap、AQS、Redis 分布式锁(CAS 思想)
3. CAS 代码示例
示例 1:原生 Unsafe 手写 CAS 计数器(最底层)
java
运行
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class CasUnsafeDemo {
// 共享变量
private volatile int count = 0;
// Unsafe对象
private static final Unsafe UNSAFE;
// count变量内存偏移地址
private static final long COUNT_OFFSET;
static {
try {
// 反射获取Unsafe实例
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
UNSAFE = (Unsafe) unsafeField.get(null);
// 获取count字段内存偏移
COUNT_OFFSET = UNSAFE.objectFieldOffset(CasUnsafeDemo.class.getDeclaredField("count"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 基于CAS实现自增
public void increment() {
// 自旋:CAS失败就循环重试
while (true) {
int oldVal = UNSAFE.getIntVolatile(this, COUNT_OFFSET);
int newVal = oldVal + 1;
// CAS:如果当前内存值=oldVal,更新为newVal,返回true成功
boolean success = UNSAFE.compareAndSwapInt(this, COUNT_OFFSET, oldVal, newVal);
if (success) {
break;
}
// CAS失败,循环重试(自旋)
}
}
public int getCount() {
return UNSAFE.getIntVolatile(this, COUNT_OFFSET);
}
public static void main(String[] args) throws InterruptedException {
CasUnsafeDemo demo = new CasUnsafeDemo();
// 10个线程,每个自增10000次
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
demo.increment();
}
});
threads[i].start();
}
// 等待所有线程完成
for (Thread t : threads) t.join();
// 输出预期100000,无并发丢失
System.out.println("最终计数:" + demo.getCount());
}
}
示例 2:JDK 封装 AtomicInteger(业务常用,屏蔽 Unsafe 底层)
java
运行
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerCasDemo {
// 原子整数,底层封装CAS
private static final AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count.getAndIncrement(); // 内部就是CAS自旋
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
// 输出10000,线程安全
System.out.println("count = " + count.get());
}
}
示例 3:CAS 经典问题:ABA + 解决方案 AtomicStampedReference
ABA 问题说明
线程 A 读取值 A,线程 B 先改成 B 再改回 A;线程 A 执行 CAS 时判断相等,误以为没修改,造成数据错乱。
java
运行
import java.util.concurrent.atomic.AtomicStampedReference;
public class CasABADemo {
// 带版本戳的原子引用,解决ABA
static AtomicStampedReference<Integer> ref = new AtomicStampedReference(100, 1);
public static void main(String[] args) throws InterruptedException {
// 线程1:模拟读取后阻塞
Thread t1 = new Thread(() -> {
int stamp = ref.getStamp();
Integer oldVal = ref.getReference();
try {
Thread.sleep(1000); // 睡眠,让t2修改
} catch (InterruptedException e) {
e.printStackTrace();
}
// CAS:同时校验值+版本号
boolean success = ref.compareAndSet(oldVal, 200, stamp, stamp + 1);
System.out.println("线程1CAS结果:" + success);
});
// 线程2:完成A->B->A
Thread t2 = new Thread(() -> {
int stamp = ref.getStamp();
ref.compareAndSet(100, 101, stamp, stamp + 1);
ref.compareAndSet(101, 100, ref.getStamp(), ref.getStamp() + 1);
System.out.println("线程2完成ABA修改");
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
输出:线程 1CAS 结果:false,成功拦截 ABA 问题。
4. CAS 优缺点总结
优点
- 用户态无锁,不切换内核,性能远超 synchronized 重量级锁
- 无线程阻塞,不会出现线程挂起唤醒开销
缺点
- 自旋循环空耗 CPU,高并发竞争激烈时 CPU 占用飙升
- ABA 数据一致性问题
- 仅支持单个变量原子操作,无法同时操作多个共享变量
三、AQS 完整详解
1. AQS 应用场景(JDK 所有同步工具底层)
独占锁模式(state 标记持有锁)
ReentrantLock可重入排他锁(最常用)ReentrantReadWriteLock.WriteLock写锁(独占)
共享锁模式(state 代表许可 / 资源)
ReentrantReadWriteLock.ReadLock读锁(共享,多线程同时读)CountDownLatch线程等待计数器(等待所有任务完成)Semaphore信号量(限流、控制并发线程数)CyclicBarrier循环栅栏(多线程到达屏障同时执行)
2. AQS 技术栈分层
底层依赖
- CAS:修改 state 同步状态、CLH 队列头 / 尾节点
- volatile:state、Node 节点变量保证可见性
- LockSupport.park/unpark:阻塞 / 唤醒等待队列线程(底层调用 Unsafe)
中层:AQS 框架 java.util.concurrent.locks.AbstractQueuedSynchronizer
上层实现类(业务直接使用)
ReentrantLock、ReadWriteLock、CountDownLatch、Semaphore
上层业务框架
线程池 ThreadPoolExecutor、分布式本地限流、自定义锁工具
3. AQS 核心执行流程(通俗版以 ReentrantLock 独占锁举例)
- 线程调用 lock (),调用 AQS
tryAcquire(),CAS 尝试把 state 从 0 改为 1- 修改成功:当前线程持有锁,记录持有线程
- 修改失败:抢锁失败,封装 Node 加入 CLH 双向等待队列
- 队列中线程调用
LockSupport.park()阻塞休眠,不再占用 CPU - 持有锁线程 unlock () 释放锁:CAS 把 state 减为 0
- 唤醒队列头部第一个等待线程,再次竞争锁
4. AQS 代码示例
示例 1:AQS 独占模式:ReentrantLock 基础使用(业务最常用)
java
运行
import java.util.concurrent.locks.ReentrantLock;
public class AqsReentrantLockDemo {
// 基于AQS实现的可重入独占锁
private static final ReentrantLock lock = new ReentrantLock();
private static int num = 0;
public static void add() {
lock.lock(); // 底层调用AQS acquire()
try {
num++;
} finally {
lock.unlock(); // 底层调用AQS release(),必须finally释放
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) add();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) add();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("num = " + num); // 20000,线程安全
}
}
示例 2:AQS 共享模式:CountDownLatch 等待多线程完成
CountDownLatch 底层 AQS 共享模式,state 初始为计数器,countDown () CAS 减少 state,state=0 唤醒所有等待线程
java
运行
import java.util.concurrent.CountDownLatch;
public class AqsCountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// AQS state初始=5
CountDownLatch latch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "任务执行完成");
latch.countDown(); // CAS将state-1
}).start();
}
latch.await(); // 阻塞,直到state=0
System.out.println("所有子线程执行完毕,主线程继续");
}
}
示例 3:手写简易独占锁(自定义 AQS 实现,看懂 AQS 模板方法)
AQS 采用模板方法模式,只需要重写
tryAcquire/tryRelease,排队、阻塞、唤醒逻辑 AQS 内部封装好java
运行
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Condition;
// 自定义独占锁,基于AQS实现
class MyAqsLock implements Lock {
// 内部同步器,继承AQS
private static class Sync extends AbstractQueuedSynchronizer {
// 尝试获取独占锁:state=0无锁,CAS改为1成功获取
@Override
protected boolean tryAcquire(int arg) {
// CAS修改state,底层依赖Unsafe
return compareAndSetState(0, 1);
}
// 释放锁:state置0
@Override
protected boolean tryRelease(int arg) {
setState(0);
return true;
}
// 是否持有独占锁
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
}
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1); // AQS内置:抢锁失败入队阻塞
}
@Override
public void unlock() {
sync.release(1); // AQS内置:释放锁,唤醒队列线程
}
// 其余接口简单实现省略
@Override public boolean tryLock() { return sync.tryAcquire(1); }
@Override public Condition newCondition() { return sync.newCondition(); }
@Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }
@Override public boolean tryLock(long time, java.util.concurrent.TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(time)); }
}
// 测试自定义AQS锁
public class CustomAqsDemo {
private static final MyAqsLock lock = new MyAqsLock();
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
lock.lock();
try { count++; } finally { lock.unlock(); }
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
lock.lock();
try { count++; } finally { lock.unlock(); }
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count); // 2000
}
}
5. AQS 优缺点
优点
- 统一同步框架,一套底层实现所有锁 / 同步工具,复用性极强
- 竞争激烈时线程 park 阻塞,不会像 CAS 无限自旋耗 CPU
- 支持独占、共享、可重入、公平 / 非公平锁多种能力
缺点
- 底层封装复杂,理解门槛高
- 线程阻塞唤醒存在少量内核态切换开销,性能略低于纯 CAS 无锁
四、CAS 和 AQS 核心关联(重中之重)
- CAS 是 AQS 的底层原子操作基础
AQS 中所有修改 state、队列头尾节点、节点状态的操作,全部使用
compareAndSetState(CAS),不加 synchronized 保证原子性。源码片段(AQS 内部):java运行// CAS修改同步状态 protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } - 分层使用场景区分
- 轻量无竞争:只用 CAS(AtomicInteger、简单计数),无队列、无阻塞,性能拉满
- 高并发大量竞争:AQS(锁、CountDownLatch),抢不到锁线程入队阻塞,避免 CAS 空自旋
- 性能取舍
- 低并发:CAS > AQS(无阻塞)
- 高并发大量竞争:AQS > CAS(CAS 自旋 CPU 打满,AQS 线程休眠)
五、CAS vs AQS 场景选型对照表
表格
| 维度 | CAS(原子类 / 无锁) | AQS(锁 / 同步器) |
|---|---|---|
| 底层 | CPU 原子指令,无阻塞自旋 | 底层 CAS+CLH 队列 + LockSupport 阻塞 |
| 适用场景 | 单个变量并发修改、低竞争计数 | 多代码块同步、资源限流、线程等待 |
| 并发冲突 | 冲突时循环自旋,消耗 CPU | 冲突线程入队挂起,释放 CPU |
| 功能 | 仅支持单变量原子操作 | 支持独占 / 共享、可重入、公平锁、条件等待 |
| 代表工具 | AtomicInteger、ConcurrentHashMap 节点 | ReentrantLock、CountDownLatch、Semaphore |
| 典型业务 | 接口请求计数、序列号生成 | 接口并发控制、多线程任务汇总、读写分离 |
六、完整技术栈全景
CAS 全链路技术栈
硬件 CPU cmpxchg → Unsafe native CAS → atomic 原子包 → ConcurrentHashMap → AQS 底层依赖 → Redis 乐观锁、数据库 version 乐观锁
AQS 全链路技术栈
CAS + volatile + LockSupport → AbstractQueuedSynchronizer → ReentrantLock/ReadWriteLock/CountDownLatch/Semaphore → ThreadPoolExecutor 线程池、分布式本地限流、自定义同步工具
七、业务开发选型建议
- 简单数字统计、单点变量并发修改 → 优先
AtomicXXX(CAS) - 一段代码 / 方法需要整体同步、需要可重入、公平锁 →
ReentrantLock(AQS 独占) - 多线程读少写多、读写分离 →
ReentrantReadWriteLock(AQS 读写共享) - 控制同时执行线程数量(限流)→
Semaphore(AQS 共享) - 主线程等待一批子线程全部完成 →
CountDownLatch(AQS 共享) - 超高并发、竞争极端激烈,用 CAS 会 CPU100% → 改用 AQS 锁

浙公网安备 33010602011771号