CAS机制
2025-01-22 08:19:30    670 字   
This post is also available in English and alternative languages.

CAS 全称是 CompareAndSwap,即:比较并替换。在 Java 中的 Unsafe 类中有对其包装的方法。


1. CAS

CAS 底层是在 CPU 层面实现的,依赖 MESI(缓存一致性) 协议,缓存行的四种状态:Modified(已修改)、Exclusive(独占)、Shared(共享)、Invalid(失效)。多核 CPU 中,缓存行都处于这四种状态之一。

更多 MESI 详阅:计算机组成原理_CPU缓存


MESI 流程如下:

  1. 假设 Core1、Core2 都持有相同地址对应的缓存行,并且它们各自的状态都是 Shared(共享)。

  2. 如果此时 Core1 想要成功修改,首先要把缓存行的状态由 Shared(共享)改为 Exclusive(独占),即 Core1 向总线广播 Exclusive(独占)状态请求,获取当前对应缓存行数据的所有权,Core2 嗅探到Exclusive(独占)请求,会将自己 Core 所属的 cache line(缓存行)状态设置为失效(Invalid)。

  3. 但如果此时 Core1、Core2 同时向总线广播 Exclusive(独占)状态请求,那就会触发总线仲裁,看 Core1、Core2 谁能获胜,获胜者就能获取 cache line(缓存行)的独占;失败者则将自己 Core 所属的 cache line(缓存行)状态设置为失效(Invalid)。


CAS 操作包含三个操作数:内存位置、预期值、新值。

CAS 的实现逻辑是:将内存位置处的数值与预期值进行比较,如果相等,则将内存位置处的值替换为新值;或不等,则不做任何操作。

直白的说:多个 Core 同时执行针对同一地址的CAS指令时,其实是在试图获取缓存行的独占权

在多核 CPU 中,通过将 CMPXCHG 指令与 LOCK 前缀指令结合起来,实现以原子方式执行比较和交换操作。

  1. 一般情况下 CAS 操作都会采用自选的方式,也就是说当发生 CAS 冲突而导致失败时,会尝试自旋一定的次数,这种情况下如果并发数高,会导致 CAS 一直在不断地重复执行进入忙等待状态,这个过程非常耗 CPU 性能。

  2. CAS 虽然在应用层面是无锁的,但在 CPU 层面其实是通过 LOCK 前缀指令保证原子性。


2. ABA 问题

有两个核心 Core1 和 Core2,Core1 从内存中读取到的数据为A,在 Core1 进行修改之前,Core2 通过 CAS 将值改为了 B,然后又改回了 A。此时 Core1 继续执行,依旧可以 CAS 成功,但过程中有其他 Core 修改了数据。

常用解决办法是加版本号解决。如 Java 中 AtomicStampedReference 类的 CAS 实现了版本号机制。