不会CAS也敢来面试——CAS原理解析

目录

    • CAS原理解析
      • java CAS简介
      • 代码说明
        • 执行过程
      • CAS底层原理
        • 源码调试
      • CAS存在的问题
      • 最后

CAS原理解析

java CAS简介

  • CAS全称:Compare-And-Swap,即比较并替换。比较变量现在的值和以前的值是否一致,若一致则替换,否则不替换
  • CAS作用:原子性更新变量值,保证线程安全
  • CAS指令:需要有三个操作数,变量的当前值(V),旧的预期值(A),准备设置的新值(B)
  • CAS指令执行条件:当前仅当V=A时,处理器才会设置V=B否则不执行更新
  • CAS的返回值:V的之前值
  • CAS处理过程:原子操作,执行期间不会被其他线程中断,线程安全
  • CAS并发原语:体现在Java语言中sun.misc.Unsafe类的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令,这是一种完全依赖于硬件的功能,通过它实现了原子操作。由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,所以CAS是一条CPU的原子指令,不会造成所谓的数据不一致的问题,所以CAS是线程安全的。

代码说明

​ 这里我们使用Atomiclnteger来演示:

public class CASDemo {
     
    public static void main(String[] args) {
     
        // 首先设置变量的初始值为10,主内存中的值设置为10
        AtomicInteger atomicInteger = new AtomicInteger(10);
        // 调用atomicInteger的CAS方法,先比较当前值是否为10,如果是则替换为20,不是则不替换
        Boolean result1 = atomicInteger.compareAndSet(10,20);
        System.out.printf("当前atomicInteger变量的值:%d 比较结果%s\r\n", atomicInteger.get(), result1);
        Boolean result2 = atomicInteger.compareAndSet(10,30);
        System.out.printf("当前atomicInteger变量的值:%d, 比较结果%s\n" , atomicInteger.get(), result2);
    }
}

结果:

当前atomicInteger变量的值:20 比较结果true
当前atomicInteger变量的值:30, 比较结果true

执行过程

  • 第一步:线程1和线程2都有主内存中变量的拷贝,值都为10
  • 第二步:线程1想要将值更新为20,先要将工作内存中的变量值与主内存中的变量进行比较,值都等于10,所以可以将内存找那个的值替换为20
  • 第三步:线程1将主内存中的值替换成20,并将线程1 中的工作内存中的副本更新为20
  • 第四步:线程2想要将变量更新为30,先要将线程2的工作内存中的值与主内存进行比较10不等于20,所以不能更新
  • 第五步:线程2将工作内存的副本更新为与主内存一致:20

CAS底层原理

源码调试

​ 这里我们用atomicInteger的getAndIncrement()方法来讲解,这个方法里面涉及到了比较并替换的原理。

源码:

public class CASDemo {
     
    public static void main(String[] args) throws InterruptedException{
     
        AtomicInteger atomicInteger = new AtomicInteger(10);
        Thread.sleep(100);

        new Thread(()->{
     
            atomicInteger.getAndIncrement();
        },"zhonghu").start();
        atomicInteger.getAndIncrement();

    }
}

​ 我们用debug模式进行调试,看这个方法里面到底做了什么操作,首先在第九行打断点,此时子线程zhonghu还没执行自增操作。

​ getAndIncrement方法会调用unsafe的getAndAddInt方法

public final int getAndAdd(int delta) {
     
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

​ 在unsafe中:

    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;
    }

在361行打上断点,让程序执行到这个位置

此时:

  • var1:当前对象,我们定义的atomicInteger
  • var2:当前对象的内存偏移量
  • var4:当前自增多少,默认为1,且不可设为其他值
  • var5:当前变量的值
  • this.getIntVolatile(var1,var2):根据当前对象var1和对象的内存偏移量var2得到主内存中变量的值,赋值给var5,并在main线程的工作内存中存放一份var5的副本

继续往下走一步,var获取到主内存中的值为10。

然后切换到子线程zhonghu,还在361行断点处,还未获取到主内存的值

子线程继续执行一步,获取到var5的值等于10

切换到main线程,进行比较并替换

​ 先比较线程中的副本是否与主内存相等,相等则可以进行自增,并返回 副本的值,若其他线程修改了主内存中的值,当前线程不能进行自增,需要重写获取主内存的值,然后再次判断是否与主内存中的值是否相等,以此往复

CAS存在的问题

​ 大家可以发现,在子线程中可以会出现循环多次的问题,因为其他线程可能将主内存的值又改了。但子线程还是拿到的是老的数据,就回出现再循环一次,就给CPU带来性能开销,这就是自旋

  • 频繁出现自旋,循环时间长,开销大(因为执行的是do-while,如果比较不成功则hi一直循环,最差的情况,就是某个线程一直取到的值和预期值不一样,这样就会无限循环下去)
  • 只能保证一个共享变量的原子操作
    • 当对一个共享变量执行操作时,我们可以通过CAS的方式来保证原子操作
    • 但是对于多个共享变量操作时,喜欢CAS就无法保证操作的原子性,这和个时候只能用锁来保证原子性

最后

  • 如果觉得看完有收获,希望能给我点个赞,这将会是我更新的最大动力,感谢各位的支持
  • 欢迎各位关注我的公众号【java冢狐】,专注于java和计算机基础知识,保证让你看完有所收获,不信你打我
  • 如果看完有不同的意见或者建议,欢迎多多评论一起交流。感谢各位的支持以及厚爱。

你可能感兴趣的:(java,多线程以及并发,java,多线程,编程语言,并发编程,jvm)