菜鸟笔记
提升您的技术认知

java的CAS机制是什么

java的CAS机制是什么?

一,什么是CAS

CAS的全称为Compare-And-Swap,它是一条CPU并发原语.

它的功能是判断内存某个位置是否为预期值,如果是则更改为新的值,这个过程是原子的(原子性).

CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法,调用Unsafe类中的CAS方法,jvm会帮我们实现出CAS汇编指令.这是一种完全依赖于硬件的功能,通过他实现原子操作.再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题;

CAS简单机制是:比较当前工作内存中的值和主内存中的值是否相同,如果相同就执行规定任务,否则继续比较直到主内存 和工作内存中的值一致为止,一致直自旋(自旋锁)

CAS有 三个操作数 内存值V(工作内存中的值),旧的预期值A(主存中的值),要修改的新值B.当且内存值V与旧的预期值相等时,将内存值修改为B,否则一直循环去比较V和A的值,直到相等

//AtomicInteger类的getAndIncrement方法
/**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
  
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

//Unsafe类的getAndAddInt方法
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;
    }
说明:var1 AtomicInteger对象本身    
    var2 该对象值的引用地址   
    var4 需要变动的数值  
    var5 是用var1 var2找出的内存中真实的值. 用该对象当前的值与var5比较:
         如果相同,跟新var5+var4 并且返回true 
         如果不同,继续取值然后在比较,直到跟新完成.

二,什么是Unsafe类

// AtomicInteger 类的源码
public class AtomicInteger extends Number implements java.io.Serializable {
  
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
  
        try {
  
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) {
   throw new Error(ex); }
    }
     private volatile int value;
   

1:是CAS的核心类,由于java方法无法直接访问底层系统,需要通过Native方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存数据,Unsafe类存在于sun.mic包中,其内部方法可以直接操作内存,因为java中CAS操作的执行都是依赖于Unsafe类中的方法

注意:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都可以直接调用操作系统底层资源执行相应的任务

2:变量valueOffset ,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的

 /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
  
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

3:变量value用volatile修饰,保证多线程之间的可见性

private volatile int value;

三,CAS缺点

1.循环时间长开销很大,有一个do{}while()循序,成功一直循环消耗时间

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

3.ABA问题

四:原子类AtomicInteger的ABA问题?原子更新引用知道吗?

1.什么是ABA问题?

CAS算法实现一个重要前提需要提取出内存中某时刻的数据并在当下时刻比较替换,那么在这个时间差内会导致数据的变化.

比如有一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些数据操作将值变成了B,然后线程two又将V位置的数据变成了A,在线程B操作过程中线程one可能被某种原因挂起了,这之后线程one操作发现内存中比较值仍然是A,然后线程one也操作成功了—>这就是ABA问题(狸猫换太子)

尽管线程one的CAS操作成功,但不代表这个过程是不安全的

2.原子引用

AtomicReference是作用是对”对象”进行原子操作。
提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference(例如比较和交换操作)将不会使得AtomicReference处于不一致的状态。

AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,底层采用的是compareAndSwapInt实现CAS,比较的是数值是否相等,而AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。

class User{
  
    String name;
    int age;

    public User(String name, int age) {
  
        this.name = name;
        this.age = age;
    }
    public String getName() {
  
        return name;
    }
    public void setName(String name) {
  
        this.name = name;
    }
    public int getAge() {
  
        return age;
    }
    public void setAge(int age) {
  
        this.age = age;
    }
    @Override
    public String toString() {
  
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class AtomicReferenceDemo {
  

    public static void main(String[] args) {
  
        User z3 = new User("z3", 22);
        User l4 = new User("l4", 25);

        //把自定义类封装成原子引用
        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(z3);

        System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.toString());
        System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.toString());
    }
} 
true  User{
  name='l4', age=25}
false User{
  name='l4', age=25}

3.怎么解决ABA问题

使用AtomicStampReference,AtomicStampReference在cas的基础上增加了一个标记stamp(版本号,类似于时间戳),使用这个标记可以用来觉察数据是否发生变化,给数据带上了一种实效性的检验。它有以下几个参数

//参数代表的含义分别是 期望值,写入的新值,期望标记,新标记值
public boolean compareAndSet(V expected,V newReference,int expectedStamp,int newStamp);

public V getRerference();

public int getStamp();

public void set(V newReference,int newStamp);
/**
 * ABA问题的解决     AtomicStampedReference
 */
public class ABADemo {
  

    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {
  
        System.out.println("-----------------ABA问题的产生--------------------");
        new Thread("t1"){
  
            @Override
            public void run() {
  
                atomicReference.compareAndSet(100, 101);
                atomicReference.compareAndSet(101, 100);
            }
        }.start();

        new Thread("t2"){
  
            @Override
            public void run() {
  
                try {
  
                    //线程t2休眠1秒钟,确保t1完成一次ABA操作
                    sleep(1000);
                } catch (InterruptedException e) {
  
                    e.printStackTrace();
                }
                System.out.println(atomicReference.compareAndSet(100, 2020) + "\t" + atomicReference.get());
            }
        }.start();

        try {
  
            Thread.sleep(2000);
        } catch (InterruptedException e) {
  
            e.printStackTrace();
        }

        System.out.println("-----------------ABA问题的解决--------------------");

        new Thread("t3"){
  
            @Override
            public void run() {
  
                int stamp = atomicStampedReference.getStamp();
                System.out.println(getName() + "\t第一次版本号:" + stamp);
                try {
  
                    //t3线程休眠1秒中,确保t4也拿到初始的版本号
                    sleep(1000);
                } catch (InterruptedException e) {
  
                    e.printStackTrace();
                }
                atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                System.out.println(getName() + "\t第二次版本号:" + atomicStampedReference.getStamp());
                atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                System.out.println(getName() + "\t第三次版本号:" + atomicStampedReference.getStamp());
            }
        }.start();

        new Thread("t4"){
  
            @Override
            public void run() {
  
                int stamp = atomicStampedReference.getStamp();
                System.out.println(getName() + "\t第一次版本号:" + stamp);
                try {
  
                    //t4线程休眠3秒中,确保t3完成一次ABA操作
                    sleep(3000);
                } catch (InterruptedException e) {
  
                    e.printStackTrace();
                }
                boolean result = atomicStampedReference.compareAndSet(100, 2020, stamp, stamp + 1);
                System.out.println(getName() + "\t是否修改成功," + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp());
                System.out.println(getName() + "\t当前实际最新值:" + atomicStampedReference.getReference());
            }
        }.start();
    }
}
 
-----------------ABA问题的产生--------------------
true  2020
-----------------ABA问题的解决--------------------
t3 第一次版本号:1
t4 第一次版本号:1
t3 第二次版本号:2
t3 第三次版本号:3
t4 是否修改成功,false 当前最新实际版本号:3
t4 当前实际最新值:100