CAS和AQS是Java并发编程中最为重要的两个基础组件,它们共同构成了Java并发包(JUC)的核心基石。CAS是原子操作的硬件级实现,而AQS是基于CAS构建的高级同步框架,二者相辅相成,缺一不可。
一、CAS(Compare-And-Swap)深度解析
1. CAS的本质与原理
CAS是一种无锁的原子操作机制,其核心思想是:在更新内存值时,先比较当前值是否与预期值一致,若一致则更新,否则放弃更新。整个过程由CPU指令保证原子性,无需操作系统介入。
CAS操作包含三个关键参数:
- V(内存地址):需要修改的变量内存地址
- E(预期值):期望的当前值
- N(新值):要更新的新值
操作逻辑:if (V == E) { V = N; return true; } else { return false; }
在Java中,CAS操作由Unsafe类提供,例如compareAndSwapInt()方法,它最终会调用CPU的CMPXCHG指令(在x86架构上)[[source_group_web_1]]。
2. CAS的三大问题与解决方案
① ABA问题
当值从A→B→A变化时,CAS无法察觉中间变化过程,可能导致逻辑错误。
实际案例:在无锁栈实现中,若线程1读取栈顶为A,被挂起;线程2将A弹出并重新入栈A,线程1恢复后会错误地认为栈未变化[[source_group_web_2]]。
解决方案:使用AtomicStampedReference,通过版本号机制解决,每次修改都增加版本号[[source_group_web_3]]。
② 自旋开销
CAS失败后会不断重试(自旋),在高竞争场景下可能导致CPU资源浪费。
实际案例:在高并发计数器场景中,若大量线程同时更新,失败线程会持续自旋,消耗CPU资源[[source_group_web_4]]。
解决方案:
- 采用退避算法,失败后等待时间指数级增长
- 在自旋一定次数后转为使用传统锁机制
- JVM内部使用
pause指令优化自旋过程[[source_group_web_5]]
③ 单变量限制
CAS只能保证单个变量的原子性,无法直接实现多变量的原子操作。
实际案例:在转账操作中,需要同时更新两个账户余额,CAS无法直接保证这两个操作的原子性[[source_group_web_6]]。
解决方案:
- 将多个变量封装到一个对象中,使用
AtomicReference进行CAS操作 - 使用
AtomicIntegerFieldUpdater等工具类实现多字段原子更新[[source_group_web_7]]
3. CAS的实际应用场景
① 原子类实现
Java中的AtomicInteger、AtomicBoolean等原子类内部使用CAS实现无锁操作,性能远高于传统锁机制[[source_group_web_8]]。
public final int incrementAndGet() {
for (;;) {
int current = get(); // 获取当前值
int next = current + 1; // 计算新值
if (compareAndSet(current, next)) // CAS操作
return next; // 更新成功返回
}
}② 高性能并发容器ConcurrentHashMap在JDK 1.8后大量使用CAS优化,如在putVal()方法中初始化Node数组或设置sizeCtl标志位[[source_group_web_9]]。
③ 无锁数据结构
基于CAS可实现ConcurrentLinkedQueue、ConcurrentLinkedStack等无锁数据结构,在高并发场景下提供更高吞吐量[[source_group_web_10]]。
二、AQS(AbstractQueuedSynchronizer)深度解析
1. AQS的核心架构
AQS是Java并发包中构建锁和同步器的基础框架,它通过"状态管理+队列排队"实现线程同步,将同步器的通用逻辑与具体实现逻辑解耦[[source_group_web_11]]。
AQS包含三大核心组件:
- state变量:
volatile int类型,表示同步状态(如锁的重入次数、信号量数量) - CLH双向队列:FIFO队列,管理等待线程
- ConditionObject:条件变量,实现等待/通知机制[[source_group_web_12]]
2. AQS的两种模式
① 独占模式(Exclusive)
同一时刻只有一个线程能获取资源,如ReentrantLock、ReentrantReadWriteLock的写锁[[source_group_web_13]]。
② 共享模式(Shared)
允许多个线程同时获取资源,如Semaphore、CountDownLatch、ReentrantReadWriteLock的读锁[[source_group_web_14]]。
3. AQS的核心流程
独占锁获取流程(acquire())
- 尝试获取资源:调用
tryAcquire()(子类实现)- 若成功,直接返回
- 若失败,进入步骤2
- 入队:创建Node节点,通过CAS加入CLH队列尾部
- 阻塞:检查前驱节点状态,若为SIGNAL则park阻塞
- 唤醒:被前驱节点唤醒后,再次尝试获取资源[[source_group_web_15]]
独占锁释放流程(release())
- 尝试释放资源:调用
tryRelease()(子类实现) - 唤醒后继:若state变为0,唤醒队列中第一个有效节点[[source_group_web_16]]
4. AQS的实际应用场景
① ReentrantLock实现ReentrantLock通过AQS实现可重入锁,state表示锁的重入次数:
- state = 0:无锁状态
- state > 0:锁被持有,值表示重入次数
- 公平锁与非公平锁的区别在于:非公平锁允许新线程插队获取锁,而公平锁严格按队列顺序[[source_group_web_17]]
② Semaphore实现Semaphore使用AQS实现信号量控制:
- state表示可用许可证数量
- acquire():尝试获取许可证,state-1
- release():释放许可证,state+1[[source_group_web_18]]
③ CountDownLatch实现CountDownLatch使用AQS实现倒计时门锁:
- state表示剩余计数
- await():等待state=0
- countDown():state-1,当state=0时唤醒所有等待线程[[source_group_web_19]]
5. AQS的最佳实践
① 自定义同步器
通过继承AQS并实现tryAcquire()、tryRelease()等方法,可快速构建自定义同步器:
public class LeeLock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
return compareAndSetState(0, 1); // 尝试获取锁
}
@Override
protected boolean tryRelease(int arg) {
setState(0); // 释放锁
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1; // 检查是否持有锁
}
}
private Sync sync = new Sync();
public void lock() {
sync.acquire(1); // 获取锁
}
public void unlock() {
sync.release(1); // 释放锁
}
}② 注意事项
- 避免死锁:确保
tryAcquire()和tryRelease()方法不会抛出异常 - 公平性权衡:非公平锁吞吐量更高,但可能导致线程饥饿
- 资源泄漏:确保finally块中释放锁,避免资源泄漏[[source_group_web_20]]
三、CAS与AQS的关系与区别
1. 二者关系
- AQS依赖CAS:AQS通过CAS操作实现对state的原子更新和队列节点的插入/删除
- CAS是基础,AQS是框架:CAS提供原子操作原语,AQS在此基础上构建高级同步机制[[source_group_web_21]]
2. 核心区别
| 特性 | CAS | AQS |
|---|---|---|
| 定位 | 原子操作指令 | 同步框架 |
| 实现复杂度 | 低(单指令级原子性) | 高(需管理队列、状态) |
| 应用场景 | 实现无锁数据结构 | 构建高级同步工具 |
| 核心功能 | 比较并交换值 | 线程排队、阻塞唤醒 |
3. 实际开发中的选择
- 低竞争场景:优先使用CAS实现的原子类(如
AtomicInteger) - 高竞争场景:使用AQS实现的锁(如
ReentrantLock) - 复杂同步需求:基于AQS构建自定义同步器,避免重复造轮子[[source_group_web_22]]
总结与最佳实践
CAS和AQS是Java并发编程的双生基石:CAS提供硬件级的原子操作保证,而AQS在此基础上构建了灵活高效的同步框架。在实际开发中,我们应根据场景选择合适的机制:
- 简单计数场景:使用
AtomicInteger等原子类,避免锁开销 - 高并发同步:使用
ReentrantLock等AQS实现的锁,注意选择公平/非公平模式 - 复杂同步需求:基于AQS构建自定义同步器,遵循"模板方法模式"
特别提醒:在高并发系统中,过度依赖CAS可能导致CPU资源浪费,而过度使用锁又可能导致吞吐量下降,最佳实践是根据实际业务场景进行权衡,通常在中低竞争场景使用CAS,高竞争场景使用AQS锁机制[[source_group_web_23]]。
作为架构师,我建议在设计系统时,先分析业务场景的并发特性,再选择合适的同步机制,避免"为锁而锁"或"为无锁而无锁"的极端思维,找到性能与可维护性的最佳平衡点。