2.1 compareAndSet

compareAndSet 比较并交换

  1. package s02.e02;
  2. import java.util.concurrent.atomic.AtomicInteger;
  3. public class CASDemo {
  4. public static void main(String[] args) {
  5. AtomicInteger atomicInteger = new AtomicInteger(5);
  6. System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data:" + atomicInteger.get());
  7. System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t current data:" + atomicInteger.get());
  8. }
  9. }

image.png
image.png

2.2 CAS 底层原理

image.png

2.2.1 UnSafe 类是什么

image.png
是 CAS 的核心类,由于 Java 方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe 相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe 类存在于 sun.misc 包中,其内部方法操作可以像 C 的指针一样直接操作内存,因为 Java 中 CAS 操作的执行依赖于 Unsafe 类的方法。
注意 Unsafe 类中的所有方法都是 native 修饰的,也就是说 Unsafe 类中的方法都直接调用操作系统底层资源执行相应任务
变量 valueOffset,表示该变量值在内存中的偏移地址,因为 Unsafe 就是根据内存偏移地址获取数据的。
变量 value 用 volatile 修饰,保证了多线程之间的内存可见性。

2.2.2 CAS 是什么

CAS 的全称为 Compare-And-Swap,它是一条 CPU 并发原语。
它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
CAS 并发原语体现在 JAVA 语言中就是 sun.misc.Unsafe 类中的各个方法。调用 UnSafe 类中的 CAS 方法,JVM 会帮我们实现出 CAS 汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于 CAS 是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说 CAS 是一条 CPU 的原子指令,不会造成所谓的数据不一致问题。
image.png
o:AtomicInteger对象本身。
offset:该对象值得引用地址。
delta:需要变动的数量。
v:是用过 o 和 offset 找出的主内存中真实的值。
用该对象当前的值与 v 比较:

  1. 如果相同,更新 v+delta 并且返回 true
  2. 如果不同,继续取值然后再比较,直到更新完成。

    2.2.3 unsafe.getAndAddlnt

    假设线程 A 和线程 B 两个线程同时执行 getAndAddInt 操作(分别跑在不同 CPU 上)

  3. AtomicInteger里面的value原始值为3,即主内存中 AtomicInteger 的 value 为 3,根据 JMM 模型,线程 A 和线程 B 各自持有一份值为 3 的 value 的副本分别到各自的工作内存。

  4. 线程 A 通过 getIntVolatile(var1, var2)拿到 value 值 3,这时线程 A 被挂起。
  5. 线程 B 也通过 getIntVolatile(var1, var2)方法获取到 value 值 3,此时刚好线程 B 没有被挂起并执行 compareAndSwaplnt 方法比较内存值也为 3,成功修改内存值为 4,线程 B 打完收工,一切 OK。
  6. 这时线程 A 恢复,执行 compareAndSwaplnt 方法比较,发现自己手里的值数字 3 和主内存的值数字 4 不一致,说明该值已经被其它线程抢先一步修改过了,那 A 线程本次修改失败,只能重新读取重新来一遍了。
  7. 线程 A 重新获取 value 值,因为变量 value 被 volatile 修饰,所以其它线程对它的修改,线程 A 总是能够看到,线程 A 继续执行 compareAndSwaplnt 进行比较替换,直到成功。

    2.2.4 底层汇编

    Unsafe 类中的 compareAndSwapInt,是一个本地方法,该方法的实现位于 unsafe.cpp 中

    1. UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNEnv *env, jobject unsafe , jobject obj, jong offset, int e , jint x))
    2. UnsafeWrapper("Unsafe_CompareAndSwaplnt");
    3. oop p = JNlHandles::resolve(obj);
    4. jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
    5. return (jint)(Atomiccmpxchg(x, addr, e))== e;
    6. UNSAFE_END
    7. // 先想办法拿到变量 value 在内存中的地址。
    8. // 通过 Atomic:.cmpxchg 实现比较替换,其中参数 x 是即将更新的值,参数 e 是原内存的值。

    2.3 总结

  8. CAS(CompareAndSwap)

比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。

  1. CAS 应用

CAS 有 3 个操作数,内存值 V,旧的预期值 A,要修改的更新值 B。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做。

2.4 CAS 缺点

  1. 循环时间长开销很大

我们可以看到 getAndAddInt 方法执行时,有个 do while,如果 CAS 失败,会一直进行尝试。如果 CAS 长时间一直不成功,可能会给 CPU 带来很大的开销。

  1. 只能保证一个共享变量的原子操作

当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

  1. 引出来 ABA 问题