—— CAS(Compare And Swap)
没有 CAS 之前,多线程环境下不使用原子类保证线程安全 i++,只能通过 synchronized 加锁的方式,高并发多写情况下,性能影响很大;使用 CAS 之后,可以使用原子类(Atomic
)保证线程安全,类似于 乐观锁
基本概念 & 底层原理
原理
- CAS(compare and swap):比较并交换,是一条 CPU 并发原语,它包含 3 个操作数——内存位置、预期原值、更新值(主内存值、工作内存值、更新值)
- 执行 CAS 操作的时候,将内存位置的值与预期原值比较;
- 如果 匹配, 那么处理器会自动将 位置值 更新为 新值
- 如果 不匹配,处理器不做任何操作,多个贤臣同时执行 CAS 操作 只有一个会成功
- 当且仅当旧的预期值 和 内存值 相同时,将 内存值 修改为 更新值,否则什么都不做或重来,当它重来重试的这种行为称为 自旋
硬件保证
- CAS 是 JDK 提供的非阻塞原子性操作,它通过 硬件 保证了比较-更新的原子性,它是非阻塞的且自身具有原子性,效率更高,且通过硬件保证,更可靠
- CAS 是一条 CPU 的原子指令(cmpxchg指令【compare x change】),不会造成所谓的数据不一致问题,Unsafe 提供的 CAS 方法底层实现即为 CPU 指令 cmpxchg
- 执行cmpxchg指令时,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行 CAS 操作,也就是说 CAS的原子性实际上是 CPU 实现独占的,比起用 synchronized 重量级锁,这里的排他时间要短很多,所以在多线程情况下性能会更好
源码说明
public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}
- 参数说明
- this:表示要操作的对象
- valueOffset:表示要操作对象中属性地址的偏移量
- expect:表示需要修改数据的期望的值
- update:表示需要修改为的新值
Unsafe 类详解
- 是 CAS 的核心类,由于 Java 方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe 相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe 类存在于 sun.misc 包中,其内部方法操作可以像 C 的指针一样直接操作内存,因为 Java 中 CAS 操作的执行依赖于 Unsafe 类的方法
- Unsafe 类中的所有方法都是 native 修饰的,所以 Unsafe 类中的方法都直接调用操作系统底层资源执行相应任务
- 变量 valueOffset,Unsafe 就是根据内存偏移地址来获取数据的
- 变量 value 用 volatile 修饰,保证了多线程之间的内存可见性
- AtomicInteger 类主要利用 CAS + volatile + native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升
- CAS 并发原语体现在 Java 语言中就是 sun.misc.Unsafe 类中的各个方法。调用 Unsafe 类中的 CAS 方法,JVM 会帮我们实现出 CAS 汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。由于 CAS 是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被打断,也就是说 CAS 是一条 CPU 的原子指令,不会造成所谓的数据不一致问题
总结
- CAS 是靠硬件实现的,从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性
- 实现方式是基于硬件平台的汇编指令,在 intel 的 CPU 中(X86机器),使用的是汇编指令 cmpxchg 指令
- 核心思想是:比较要更新变量的值V和预期值E,相等才会将V的值设置为新值 N,如果不相等自旋
缺点
- 自旋引起的循环时间长,CPU 开销大
- ABA 问题
- CAS 算法实现一个重要前提是,需要取出内存中某个时刻的数据并在当下时刻比较并交换,那么在这个时间差会导致数据的变化
- 比如一个线程A 从内存位置取出 1,另一个线程B 也从内存中取出 1,并且线程 B 进行了一些操作将 1 改为 2,然后线程B 又将 2 改回了 1,这时线程 A 进行CAS 操作发现内存中仍然是 1,预期 OK ,进行操作更改
- 尽管线程 A 的 CAS 操作成功,但是不代表这个过程就是没有问题的
通过版本号戳记流水原子引用(AtomicStampedReference)可解决 ABA 问题
—— 原子类
基本类型原子类
- AtomicInteger
- AtomicBolean
- AtomicLong
数组类型原子类
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
引用类型原子类
AtomicReference
AtomicStampedReference
- 携带版本号的引用类型原子类,可以解决 ABA 问题
- 解决修改过几次的问题
- 版本号流水戳原子引用
AtomicMarkableReference
- 原子更新带有标记位的引用类型对象
- 解决是否修改过(就是将版本号简化为 true|false,类似于一次性使用)
- 状态戳(true、false)原子引用
对象的属性修改原子类
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
- AtomicReferenceFieldUpdater
原子操作增强类
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder