CAS

概述?

  1. CAS的全称为Compare-And-Swap ,它是一条CPU并发原语 ,比较 工作内存值(预期值) 和 主物理内存 的共享值是否相同,相同则执行规定操作,否则继续比较直到主内存和工作内存的值一致为止。这个过程是原子的

    (AtomicInteger类主要利用 CAS(compare and swap)+volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升)

1661670663212

  1. CAS并发原语是现在 Java 语言中就是 sun.misc包下的 UnSafe 类中的各个方法。调用 UnSafe 类中的 CAS 方法, JVM 会帮我实现 CAS 汇编指令。这是一种完全依赖于硬件功能 ,通过它实现了原子操作。

    由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS 是一条原子指令,不会造成所谓的数据不一致的问题

  2. atomicInteger.getAndIncrement()

    1
    2
    3
    public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    调用了 unsafe.getAndAddInt();

    1
    2
    3
    4
    5
    6
    7
    public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
    var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * @Date 2022/8/28 15:22
    * @Author c-z-k
    */
    public class AtomicIntDemo {
    public static void main(String[] args) {
    AtomicInteger atomicInteger = new AtomicInteger();
    int i = atomicInteger.incrementAndGet();
    System.out.println("i = " + i);

    int andIncrement = atomicInteger.getAndIncrement();
    System.out.println("andIncrement = " + andIncrement);
    System.out.println("atomicInteger.compareAndSet(2,3) = " + atomicInteger.compareAndSet(2, 3));
    System.out.println("atomicInteger.compareAndSet(2,3) = " + atomicInteger.compareAndSet(2, 3));
    }
    }

Unsafe

UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务

  1. 是 CAS 的核心类,由于 Java 方法无法直接访问底层 ,需要通过本地(native)方法来访问, UnSafe 相当于一个后门,基于该类可以直接操作特定的内存数据。UnSafe类在于 sun.misc 包中,其内部方法操作可以向 C 的指针一样直接操作内存,因为 Java 中 CAS 操作依赖于UnSafe 类的方法
  2. AtomicInteger 中使用 unsafe。

1661675223957

  • 变量 ValueOffset ,便是该变量在内存中的偏移地址,因为 UnSafe 就是根据内存偏移地址获取数据的,变量 value 和 volatile 修饰,保证了多线程之间的可见性

CAS的缺点

  1. 循环时间长开销很大

    • 我们可以看到getAndInt方法执行时,有个do while
    • 如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销
  2. 只能保证一个共享变量的原子性

    • 当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作
    • 对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁来保证原子性
  3. ABA问题的产生

    比如一个线程 one 从内存位置 V 中取出A,这时候另一个线程 two 也从内存中取出 A,并且线程 two 进行了一些操作将值变成了B,然后线程 two又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是A,然后线程 one 操作成功。尽管线程 one 的CAS操作成功,但是不代表这个过程就是没问题的

    代码演示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    package tech.chen.juccode.a13;

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicBoolean;

    /**
    * @Date 2022/8/28 16:33
    * @Author c-z-k
    */
    public class ABADemo {
    public static void main(String[] args) {
    AtomicBoolean atomicBoolean = new AtomicBoolean(true);

    new Thread(()->{
    atomicBoolean.compareAndSet(true,false);
    System.out.println(Thread.currentThread().getName() +"\t"+atomicBoolean.get());
    },"t1").start();
    try {
    TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    new Thread(()->{
    atomicBoolean.compareAndSet(false,true);
    System.out.println(Thread.currentThread().getName() +"\t"+atomicBoolean.get());
    },"t2").start();

    try {
    TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    atomicBoolean.compareAndSet(true,false);
    System.out.println("atomicBoolean.get() = " + atomicBoolean.get());
    }
    }

ABA 问题的解决方案

  1. ABA问题解决方案是使用 AtomicStampedReference 每修改一次都会有一个版本号
  2. AtomicStampedReference 用来解决 AtomicInteger 中的 ABA 问题,该 demo 企图将 integer 的值从0一直增长到1000,但当integer 的值增长到128后,将停止增长。出现该现象有两点原因:
    1. 使用int类型而非 Integer 保存当前值
    2. Interger对-128~127的缓存[这个范围才有效,不在这个范围comareAndSet会一直返回false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package tech.chen.juccode.a13;

import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
* @Date 2022/8/28 16:33
* @Author c-z-k
*/
public class ABAResolveDemo {
private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference(100,1);
public static void main(String[] args) {
int startStamp = stampedReference.getStamp();
Integer startReference = stampedReference.getReference();
//模拟主线程运行到一半被别的线程插入操作 1
new Thread(()->{
int stamp = stampedReference.getStamp();
Integer reference = stampedReference.getReference();
stampedReference.compareAndSet(reference,101,stamp,stamp+1);
},"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//模拟主线程运行到一半被别的线程插入操作 2
new Thread(()->{
int stamp = stampedReference.getStamp();
Integer reference = stampedReference.getReference();
stampedReference.compareAndSet(reference,100,stamp,stamp+1);
},"t2").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}

boolean b = stampedReference.compareAndSet(startReference, startReference + 1, startStamp, startStamp + 1);
System.out.println(b + "\t" + stampedReference.getReference());
}
}

运行结果:

false 100