CAS学习

#锁 #面试 #java #并发

1. CAS 是什么?

CAS 全称:Compare And Swap(比较并交换)

简单说就是:比较内存里的值和预期值是否一样,一样就更新,不一样就重试。

Java 中的 AtomicInteger 就是用 CAS 实现的,多个线程同时执行 CAS 操作,只有一个能成功,这是硬件保证的。

CAS使用.png

CAS原理.png


2. CAS 底层怎么实现的?

CAS 是个乐观锁,它假设不会有冲突,直接去改,改不了就重试。

在 CPU 层面,CAS 是通过 cmpxchg 指令实现的。但这个指令本身不是原子的,可能会被打断:

比如线程 A 正在执行 cmpxchg,发现内存值和预期值一样,准备写入新值,这时候线程 B 突然插进来把值改了,就出问题了。

怎么办?在 cmpxchg 前面加个 lock 前缀指令,通过总线锁或缓存锁来保证原子性。

所以虽然我们说 CAS 是"无锁算法",但底层其实还是加了锁的,只不过锁的粒度很小。


3. CAS 有什么问题?

3.1 ABA 问题

线程 1 读到值是 A,正在计算新值的时候,线程 2 把值从 A 改成 B,又改回 A。

这时候线程 1 用 CAS 去更新,发现值还是 A,就以为没人动过,其实已经被改过了。

什么时候会出现?
变量的值可以来回变(A → B → A)就会有这个问题。如果值只能往一个方向变(比如自增的 ID),就不会有。

怎么解决?
给变量加上版本号或时间戳,不光比较值,还比较版本号。

3.2 CPU 开销大

如果 CAS 一直失败,就会不停地循环重试,白白浪费 CPU。

高并发场景下,大量线程都在自旋,CPU 就被耗光了。

3.3 只能保证一个变量的原子性

CAS 只能对单个变量进行原子操作,多个变量就不行了。

怎么办?
Java 提供了 AtomicReference 类,可以把多个变量封装成一个对象,然后对这个对象用 CAS。