CAS

xiaopeng
发布于 2026-08-13 / 5 阅读
0
0

CAS

CAS(Compare And Swap)为什么能保证线程安全? ⭐⭐⭐⭐⭐

面试频率:★★★★★ 工作频率:★★★★★


⚡30 秒速记(复习只看这里)

🟢 一句话

CAS(Compare And Swap)是一种乐观锁机制,通过比较内存中的旧值是否发生变化,如果没有变化则更新,否则不断重试。


🟡 CAS 三个核心参数

当前值
​
期望值
​
新值

执行:

如果当前值 == 期望值
​
↓
​
替换成新值

否则:

失败

🟠 CAS 核心思想

不是:

先加锁
​
再修改

而是:

先尝试修改
​
失败再重试

所以叫:

乐观锁。


🔴 CAS 解决什么问题?

解决:

多个线程同时修改共享变量

导致的数据覆盖问题。


📚 完整知识

一、为什么需要 CAS?

看普通代码:

count++;

很多人认为:

一步完成。

实际上:

读取 count
​
↓
​
count + 1
​
↓
​
写回 count

包含多个步骤。


两个线程:

初始:

count = 0

线程A:

读取 count=0

线程B:

读取 count=0

线程A:

写入1

线程B:

写入1

最终:

count=1

但是:

正确结果应该:

count=2

这就是:

数据丢失。


二、synchronized 如何解决?

加锁:

synchronized(lock){

    count++;

}

流程:

线程A

↓

获取锁

↓

修改数据

↓

释放锁


线程B

↓

等待

↓

获取锁

↓

修改数据

安全。


但是:

问题:

竞争高时:

线程阻塞

线程切换

性能下降

三、CAS 怎么解决?

CAS:

包含三个值:

内存当前值

期望值

新值

例如:

当前:

count = 0

线程A:

希望:

0 → 1

执行:

CAS(当前值,期望值,新值)

CAS(0,0,1)

意思:

如果现在还是0,就修改成1。


成功:

0 → 1

返回:

true

如果其他线程已经修改:

例如:

count=1

线程A:

再次执行:

CAS(1,0,1)

发现:

当前值不是0

失败。

然后重新读取。


四、为什么 CAS 是线程安全的?

因为:

CAS 是 CPU 提供的原子操作。

比较和交换:

是一个不可分割的整体。

不会出现:

比较成功

↓

被其他线程修改

↓

再交换

这种情况。


五、CAS 底层原理

Java 中:

CAS 最终依赖:

CPU 原子指令

JVM:

通过:

Unsafe

调用底层 CAS。


例如:

AtomicInteger:

AtomicInteger count =
        new AtomicInteger(0);

调用:

count.incrementAndGet();

底层类似:

while(true){

    oldValue = 当前值;

    newValue = oldValue + 1;


    if(CAS(oldValue,newValue)){

        break;

    }

}

这个过程叫:

自旋。


六、什么是自旋?

CAS 失败后:

不是等待。

而是:

重新尝试。

例如:

CAS失败

↓

再次CAS

↓

再次失败

↓

继续尝试

优点:

避免线程阻塞。

缺点:

消耗 CPU。


七、CAS 的问题

1. 高竞争导致 CPU 消耗

例如:

100个线程:

同时修改:

count

结果:

一个成功

99个失败

失败线程:

不断:

读取

↓

CAS

↓

失败

↓

重试

CPU压力增加。


2. ABA 问题 ⭐⭐⭐⭐⭐

这是面试重点。


假设:

初始:

A

线程A:

读取:

A

准备修改。


线程B:

修改:

A → B

然后:

B → A

线程A回来:

看到:

还是A

认为:

没有变化。

继续修改。


但是实际上:

中间已经发生变化。

这就是:

ABA问题。


八、如何解决 ABA?

增加版本号。

普通:

A

改成:

A(version=1)

变化:

A1

↓

B2

↓

A3

线程回来:

发现:

A3 != A1

知道中间发生过变化。


Java:

提供:

AtomicStampedReference

解决 ABA。


九、CAS 和 synchronized 区别

CAS

synchronized

思想

乐观锁

悲观锁

阻塞

不会阻塞

可能阻塞

失败处理

不断重试

等待锁

适合

简单变量修改

复杂业务

竞争低

性能高

性能一般


十、CAS 和 volatile 区别

很多人容易混淆。


volatile

解决:

可见性

例如:

volatile boolean flag;

一个线程修改:

其他线程能看到。


CAS

解决:

修改过程原子性

例如:

count++;

保证:

不会被覆盖。


所以:

volatile

↓

保证看见


CAS

↓

保证修改成功

十一、为什么 ConcurrentHashMap 使用 CAS?

JDK8:

ConcurrentHashMap:

CAS

+

synchronized

桶为空:

例如:

table[5] == null

只需要:

null → Node

修改一个引用。

适合 CAS。


桶有数据:

例如:

Node

↓

Node

需要修改:

next指针

节点关系

红黑树结构

多个操作。

不适合 CAS。

使用:

synchronized

十二、AtomicInteger 为什么线程安全?

代码:

AtomicInteger count =
        new AtomicInteger();

执行:

count.incrementAndGet();

内部:

读取旧值

↓

计算新值

↓

CAS更新

↓

失败重新尝试

所以:

多线程环境安全。


💼 工作场景

计数器

错误:

private int count;


count++;

多线程:

可能数据丢失。


正确:

AtomicInteger count =
        new AtomicInteger();


count.incrementAndGet();

🎤 面试回答(30秒)

CAS 是一种乐观锁机制,通过比较内存中的旧值和期望值,如果一致则更新为新值,否则重新尝试。CAS 依赖 CPU 原子指令,因此能够保证单个变量修改的原子性。Java 中 AtomicInteger、ConcurrentHashMap 等底层都使用 CAS。但是 CAS 存在 ABA 问题和高竞争导致 CPU 消耗的问题,因此复杂场景通常结合 synchronized 使用。


🎯 面试追问

Q1:CAS 是锁吗?

答:

不是传统意义上的锁。

CAS 是一种无锁的乐观同步机制。


Q2:CAS 为什么线程安全?

答:

因为比较和交换是 CPU 原子操作,不会被线程打断。


Q3:CAS 有什么缺点?

答:

  1. 高竞争时大量自旋消耗 CPU。

  2. 存在 ABA 问题。


Q4:ABA 怎么解决?

答:

增加版本号,例如 AtomicStampedReference。


Q5:CAS 和 synchronized 怎么选择?

答:

简单变量修改使用 CAS。

复杂业务逻辑使用 synchronized。


Q6:ConcurrentHashMap 为什么 CAS 和 synchronized 一起用?

答:

空桶初始化使用 CAS。

复杂结构修改使用 synchronized。


⚠️ 工作踩坑

1. 认为 CAS 适合所有场景

错误。

链表、红黑树等复杂结构不适合。


2. 认为 CAS 没有锁所以一定更快

错误。

高竞争下大量自旋会消耗 CPU。


3. 认为 volatile 可以代替 CAS

错误。

volatile 只保证可见性。


❓ 自测

  1. CAS 是什么?

  2. CAS 三个参数是什么?

  3. CAS 为什么线程安全?

  4. 什么是自旋?

  5. CAS 有什么缺点?

  6. 什么是 ABA 问题?

  7. 如何解决 ABA?

  8. AtomicInteger 为什么线程安全?

  9. ConcurrentHashMap 为什么同时使用 CAS 和 synchronized?


评论