CAS与AQS
| 维度 | CAS | AQS |
|---|---|---|
| 定位 | CPU原子指令 / 底层并发原语 | Java实现的、构建同步器的高级框架 |
| 核心思想 | 乐观锁,无锁并发,比较并交换 | 模板方法模式,队列 + 状态管理 |
| 实现级别 | 硬件指令(汇编级别) | Java类库级别(基于CAS和队列) |
| 关注点 | 保证单个共享变量的原子性更新 | 管理多线程对共享资源的访问秩序 |
| 解决问题 | 低粒度、简单的原子操作(如i++) | 高粒度、复杂的同步控制(如锁、信号量) |
| 性能特点 | 竞争不激烈时性能极高;激烈时自旋消耗CPU | 竞争激烈时,会将线程排队挂起,避免CPU空转 |
| 关系 | 是AQS实现的基石,AQS用CAS操作state和队列节点 |
是CAS的高级使用者,将CAS、队列、线程调度结合 |

AQS(AbstractQueuedSynchronizer)
1. 是什么?
AQS是JUC(java.util.concurrent)包中一个用于构建锁和同步器的核心框架。它是一个抽象类,使用了模板方法设计模式。
2. 核心原理
AQS内部维护了一个双向FIFO队列(CLH队列的变种)来管理等待获取资源的线程,同时使用一个volatile的int类型变量state来表示同步状态。
-
state:不同的锁有不同的含义。-
在
ReentrantLock中,state表示持有锁的线程的重入次数。 -
在
Semaphore中,state表示可用的许可数量。 -
在
CountDownLatch中,state表示计数器数值。
-
-
同步队列:当线程获取资源失败时,AQS会将当前线程封装成一个
Node节点,CAS地插入队尾,然后挂起线程。当资源释放时,会唤醒队列中的线程再次尝试获取。
3. 在Java中的体现与应用
-
ReentrantLock(可重入锁) -
Semaphore(信号量) -
CountDownLatch(倒计时闩) -
ReentrantReadWriteLock(读写锁) -
ThreadPoolExecutor.Worker(线程池工作线程)
这些类内部都持有一个AQS的子类(作为内部类),并通过重写AQS的tryAcquire、tryRelease等方法,定义了各自的同步逻辑。
4. 如何工作(以ReentrantLock为例)
-
线程调用
lock()方法。 -
AQS子类尝试用CAS操作去修改
state(从0改为1)。如果成功,则获取锁,并记录当前线程。 -
如果失败(
state不为0,可能是锁被占用),则将线程加入等待队列并挂起。 -
持有锁的线程调用
unlock()时,会释放锁(修改state),并CAS地唤醒队列中的下一个线程。
可以看到,AQS内部的状态管理、入队出队操作,都大量依赖CAS来保证线程安全。
CAS(Compare-And-Swap)
1. 是什么?
CAS是一条CPU原子指令(在Java中通过sun.misc.Unsafe类或java.util.concurrent.atomic包下的原子类暴露给开发者使用)。它的操作是不可分割的。
2. 核心原理
它包含三个操作数:内存位置(V)、预期原值(A)、新值(B)。
-
检查内存位置V的值是否等于预期值A。
-
如果相等,则处理器会自动将该位置值更新为新值B。
-
如果不相等,说明有其他线程修改了V,操作失败(通常选择重试或放弃)。
伪代码表示:
boolean compareAndSwap(int memoryAddress, int expectedValue, int newValue) {
if (memoryAddress.value == expectedValue) {
memoryAddress.value = newValue;
return true;
}
return false;
}
3. 在Java中的体现与应用
-
AtomicInteger、AtomicLong、AtomicReference等:这些原子类的incrementAndGet()、compareAndSet()等方法底层都使用CAS。 -
示例:
AtomicInteger count = new AtomicInteger(0); // 以下操作是线程安全的,底层使用CAS count.incrementAndGet(); // 相当于 count++ boolean success = count.compareAndSet(1, 2); // 如果当前值是1,就改成2
4. 优点与缺点
-
优点:无锁,避免了线程挂起和调度的开销,在高并发竞争不激烈的场景下性能极高。
-
缺点:
-
ABA问题:一个值从A变成B,又变回A,CAS检查时会认为它没变。解决方案是使用带版本号的
AtomicStampedReference。 -
循环时间长开销大:如果竞争激烈,CAS长时间失败会不停重试(自旋),消耗CPU。
-
只能保证一个共享变量的原子操作。
-
举例说明:假设初始 value = 5。
-
线程A执行
incrementAndGet():-
读取
oldValue = 5。 -
计算
newValue = 6。 -
在它即将执行CAS的瞬间,线程B抢先完成了整个
incrementAndGet(),把value从5改成了6。
-
-
现在线程A执行CAS:
compareAndSwap(this.value, 5, 6)-
检查:当前内存中的
this.value已经是6了,而预期值oldValue是5。 -
结果:6 != 5,CAS失败!返回
false。
-
-
线程A进入下一轮循环:
-
重新读取
oldValue = 6(最新的值)。 -
重新计算
newValue = 7。 -
再次尝试CAS:
compareAndSwap(this.value, 6, 7)。这次如果没有其他线程干扰,就会成功。
-
更直观的AtomicInteger源码片段
看AtomicInteger.getAndIncrement()的简化版实现(OpenJDK源码思路):
public final int getAndIncrement() {
for (;;) { // 自旋循环
int current = get(); // 【读取】当前值,作为预期值
int next = current + 1; // 【计算】新值
if (compareAndSet(current, next)) { // 【CAS尝试写入】
return current; // 成功则返回旧值
}
// 失败则循环重试
}
}
public final boolean compareAndSet(int expect, int update) {
// 调用Unsafe类的native方法,底层就是CPU的CAS指令
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
如何避免CAS无限自旋?
-
指数退避(Exponential Backoff)
int retries = 0; while (!cas()) { retries++; if (retries > MAX_RETRIES) { // 回退到阻塞锁或其他策略 lock.lock(); try { // 执行操作 } finally { lock.unlock(); } break; } // 等待时间逐渐增长 Thread.sleep(Math.min(100, 1 << retries)); } -
引入随机性
while (!cas()) { // 随机等待,打破同步 Thread.sleep(ThreadLocalRandom.current().nextInt(10, 100)); } -
使用混合策略
-
先自旋尝试几次
-
失败后让出CPU(
Thread.yield()) -
再失败就进入等待队列(如AQS做的那样)
-
-
限制自旋次数
for (int i = 0; i < MAX_SPINS; i++) { if (cas()) { return success; } } // 自旋超过阈值,转用阻塞方式
Java中的实际处理
Java的AtomicInteger等类没有无限自旋的保护,它们依赖于:
-
JVM和操作系统的线程调度公平性
-
实际应用中竞争不会如此极端
-
如果真遇到极端竞争,说明应该换用阻塞同步(如
synchronized或ReentrantLock)
总结:
是的,CAS理论上可能导致线程无限自旋,尤其是:
-
竞争非常激烈,某些线程总是抢不到"窗口期"
-
线程逻辑对称,导致活锁
-
没有退避机制
在实际编程中,高竞争场景下使用CAS需要非常小心,通常要配合:
-
退避策略
-
竞争检测
-
必要时回退到阻塞同步
这也是为什么Java的AQS框架并不是纯自旋,而是将线程放入队列并挂起——它避免了无限自旋,用阻塞换取了公平性和稳定性。
| 方法 | 是否有自旋 | 作用 | 返回值 |
|---|---|---|---|
compareAndSet(expect, update) |
❌ 没有 | 单次CAS尝试 | boolean (成功/失败) |
getAndIncrement() |
✅ 有 | 原子递增(必须成功) | int (旧值) |
incrementAndGet() |
✅ 有 | 原子递增(必须成功) | int (新值) |
getAndAdd(delta) |
✅ 有 | 原子加delta(必须成功) | int (旧值) |
weakCompareAndSet() |
❌ 没有 | 单次弱内存语义CAS尝试 | boolean |
浙公网安备 33010602011771号