Skip to content

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中的AtomicIntegerAtomicBoolean等原子类内部使用CAS实现无锁操作,性能远高于传统锁机制[[source_group_web_8]]。

java
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可实现ConcurrentLinkedQueueConcurrentLinkedStack等无锁数据结构,在高并发场景下提供更高吞吐量[[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)
同一时刻只有一个线程能获取资源,如ReentrantLockReentrantReadWriteLock的写锁[[source_group_web_13]]。

② 共享模式(Shared)
允许多个线程同时获取资源,如SemaphoreCountDownLatchReentrantReadWriteLock的读锁[[source_group_web_14]]。

3. AQS的核心流程

独占锁获取流程(acquire())

  1. 尝试获取资源:调用tryAcquire()(子类实现)
    • 若成功,直接返回
    • 若失败,进入步骤2
  2. 入队:创建Node节点,通过CAS加入CLH队列尾部
  3. 阻塞:检查前驱节点状态,若为SIGNAL则park阻塞
  4. 唤醒:被前驱节点唤醒后,再次尝试获取资源[[source_group_web_15]]

独占锁释放流程(release())

  1. 尝试释放资源:调用tryRelease()(子类实现)
  2. 唤醒后继:若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()等方法,可快速构建自定义同步器:

java
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. 核心区别

特性CASAQS
定位原子操作指令同步框架
实现复杂度低(单指令级原子性)高(需管理队列、状态)
应用场景实现无锁数据结构构建高级同步工具
核心功能比较并交换值线程排队、阻塞唤醒

3. 实际开发中的选择

  • 低竞争场景:优先使用CAS实现的原子类(如AtomicInteger
  • 高竞争场景:使用AQS实现的锁(如ReentrantLock
  • 复杂同步需求:基于AQS构建自定义同步器,避免重复造轮子[[source_group_web_22]]

总结与最佳实践

CAS和AQS是Java并发编程的双生基石:CAS提供硬件级的原子操作保证,而AQS在此基础上构建了灵活高效的同步框架。在实际开发中,我们应根据场景选择合适的机制:

  1. 简单计数场景:使用AtomicInteger等原子类,避免锁开销
  2. 高并发同步:使用ReentrantLock等AQS实现的锁,注意选择公平/非公平模式
  3. 复杂同步需求:基于AQS构建自定义同步器,遵循"模板方法模式"

特别提醒:在高并发系统中,过度依赖CAS可能导致CPU资源浪费,而过度使用锁又可能导致吞吐量下降,最佳实践是根据实际业务场景进行权衡,通常在中低竞争场景使用CAS,高竞争场景使用AQS锁机制[[source_group_web_23]]。

作为架构师,我建议在设计系统时,先分析业务场景的并发特性,再选择合适的同步机制,避免"为锁而锁"或"为无锁而无锁"的极端思维,找到性能与可维护性的最佳平衡点。