CAS底层原理

CAS是什么?
CAS的全称为Compare-And-Swap,它是一条CPU并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新值,这个过程是原子的。
CAS并发原语体现在java语言中就是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一条完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成的,用于完成某个功能。原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

首先,我们先看一下什么情况下会造成数据不一致,请看如下代码:

package com.ly;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {

    volatile int i= 0;

    public void addOne(){
        i++;
    }

    public static void main(String[] args) {
        CASDemo casDemo = new CASDemo();
        for(int k=0; k<20; k++){
            new Thread(()->{
                for(int j=0; j<1000; j++)
                    casDemo.addOne();
            },k+"").start();
        }
        while(Thread.activeCount() > 2){
            Thread.yield();
        }
        System.out.println("当前i的值为:" + casDemo.i);
    }
}

第一次运行
多次运行程序我们会发现,每次的结果都是不一样的。而按照我们的预期,程序中创建20个线程,每个线程执行1000次的加一操作,最终结果应该为20000呀。是什么造成的这种不一致呢?原因就出在了代码中i++ 操作上面。由于java在编译成为字节码文件后i++操作会变成一下三个操作步骤:1.getfield,即从内存中获取i的值到线程工作内存;2.iadd,即将工作内存中的i的值加一;3.putfield,即将i的值重新写入到主内存中。由此可见,并非原子操作的i++代码在多线程的环境下执行后,会出现写覆盖问题,从而造成最终数据的不一致问题。

经过以上分析,我们再看修改过的如下代码:

package com.ly;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {

    AtomicInteger atomicInteger = new AtomicInteger();

    public void addOne(){
        atomicInteger.getAndIncrement();
    }

    public static void main(String[] args) {
        CASDemo casDemo = new CASDemo();
        for(int i=0; i<20; i++){
            new Thread(()->{
                for(int j=0; j<1000; j++)
                    casDemo.addOne();
            },i+"").start();
        }
        while(Thread.activeCount() > 2){
            Thread.yield();
        }
        System.out.println("当前atomicInteger的值为:" + casDemo.atomicInteger);
    }
}

在上面代码中我们会发现,我们使用了AtomicInteger atomicInteger = new AtomicInteger();替换了原有的变量 i 的声明,同时将i++操作变成了atomicInteger.getAndIncrement(); 重新运行代码最终结果达到了我们的预期。为什么使用了getAndIncrement()方法可以实现数据结果的最终一致性呢?我们先来看看AtomicInteger的源码实现。
在这里插入图片描述

在这里插入图片描述
从源码中我们会发现该方法直接将unsafe.getAndAddInt(this, valueOffset, 1)作为了返回结果。在进入unsafe类中看看getAndAddInt的代码实现如下:
在这里插入图片描述
在分析如上的代码前,先来简单介绍一下Unsafe类,它的主要用途是什么呢?
Unsafe,是CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因此java中CAS操作的执行依赖于Unsafe类的方法。注意,Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应操作,具有原子性。

在简单介绍一下unsafe类之后,再来看看这段代码具体执行了什么操作。代码首行声明了一个变量var5,然后进入do-while循环中执行 var5 = this.getIntVolatile(var1, var2); 这句代码的含义是,unsafe类中的该方法在传入var1(即AtomicInteger对象)以及var2(即valueOffset内存偏移量)两个参数后从内存中获取到当前atomicInteger的value值即var5,接着在while循环中执行compareAndSwapInt方法,该方法的作用是将var5与var1和var2确定的主内存中的值做比较,如果相等就说明当前值未被其他线程所修改过(注:AtomicInteger类中的value值通过volatile关键字进行修饰,一旦其他线程修改,会将工作内存中的变量值同步到主内存中。),则执行var5+var4操作也即加一操作,否则,重新获取新的var5再与var1和var2所确定的主内存中的值做比较。

最后,我们通过一个多线程同时操作共享资源的例子再来回顾一下AtomicInteger类如何采用CAS机制实现数据的最终一致性。
假设线程A和线程B两个线程同时执行getAndAddInt操作(分别跑在不同的CPU上)
1.AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存中。
2.线程A通过getIntVolatile(var1,var2)拿到value值3,这时线程A被挂起。
3.线程B也通过getIntVolatile(var1,var2)方法获取到value值3,此时刚好线程B没有被挂起并执行compareAndSwapInt方法。
4.这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值数字3和主内存的值数字4不一致,说明该值已经被其他线程抢先一步修改了,那线程A本次修改失败,只能重新读取主内存重新来一遍了。
5.线程A重新获取value值,因为变量value被volatile修饰,所以其他线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值