10. CAS 与 原子操作
10.1 原子性
原子操作就是最小的不可拆分的操作,操作一旦开始,就不能被打断,直到操作完成。
- 要么全部成功,要么全部失败。
- 原子操作能保证线程安全性。
10.1.1 Java 中的原子类
java.util.concurrent.automic
包中提供了原子操作类,保证了线程安全性和操作的高效性:
- 基本数据类型原子类:
AtomicInteger
,AtomicBoolean
等 - 数组类型原子类:
AtomicIntegerArray
,AtomicReferenceArray
等 - 引用类型原子类:
AtomicReference
等- 把多个变量放在用一个对象里进行 CAS 操作,解决 CAS 的多变量问题
AtomicStampedReference
可以解决 ABA 问题
- 字段类型原子类:
AtomicIntegerFieldUpdater
等
Java 中的原子类在提交数据更改时使用了 CAS 操作,内部实现原理是利用了 CPU 的缓存锁。
10.1.2 处理器实现原子操作
缓存锁:通过锁 CPU 缓存来保证对某个内存地址的操作是原子性的。
10.1.3 Java 实现原子操作
- CAS 机制
- 锁机制
10.2 CAS
CAS 即 compare and swap,比较并交换。
CAS 是无锁编程的一种实现方式,用来实现 自旋锁 或 乐观锁。
- 又叫非阻塞同步。
CAS 定义三个值:
- V : 要更新的变量 var
- E : 当前线程对变量旧值的预期值 exception
- N : 变量被更新的值 new
10.2.1 CAS 的更新机制
比较 V 与 E,判断 V 是否被其他线程更改过:
- 如果
V == E
,则设置V = N
- 如果
V != E
,则说明 V 被其他线程更改过,取消这次更新
10.2.2 CAS 操作的原子性
CAS 是一种系统原语,具有原子性,当多个线程同时使用 CAS 来操作一个变量时,只有一个会更新成功,其他均失败:
- 失败的线程会被告知失败,此时失败的线程可以尝试重试或者放弃重试
- 失败并重试被称为 CAS 自旋
10.2.3 CAS 的实现
Java 实现 CAS : Unsafe 类
sun.misc
包下的 Unsafe
类下的几个 native 方法实现了 CAS 操作:
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
CPU 级 CAS 的实现
cmpxchgl CPU 指令实现CAS
10.2.4 CAS 存在的问题
多变量问题
CAS 能完成对单个变量的原子操作,但不能保证多个变量同时操作的原子性
- 可以通过把多个变量封装成引用类型,通过
AtomicReference
进行 CAS 操作 - 使用锁,锁同步代码块
ABA 问题
当 CAS 中,变量的值从 A 改到了 B,有改回了 A,则 CAS 会认为变量值没有被改变过。
- 可以通过版本号机制来解决这一问题。
长时间自旋问题
一直自旋会占用大量的 CPU 资源
- 使用
pause
指令提升自旋等待循环的性能
10.2.5 CAS 的适用场景
- 适用于简单对象的操作,如 boolean,int 等
- 适用于冲突少的情况,如果太多线程在自旋,会导致过大的 CPU 开销