Java并发编程之CAS底层实现原理

什么是CAS?

CAS的全称是Compare-And-Swap(乐观锁的内部实现)

功能:用于判断内存某个位置的Value值是否为预期值A,如果是则更改为新的值B,整个操作过程是原子操作

使用场景:

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


即:CAS是线程安全的

核心属性:

1、unsafe核心类

由于Java方法无法直接访问底层操作系统,需要通过本地(Native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存数据,而Unsafe类存在sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存

因为Unsafe类的所有方法都是native修饰的,均可以直接调用操作系统底层资源执行相应的任务,之所以Atomic修饰的包装类能够保证原子性,依靠的就是底层的unsafe类

2、valueOffset变量

表示该变量值在内存中的偏移地址,Unsafe就是根据内存偏移地址获取数据,通过valueOffset内存地址,可以直接获取到值,然后进行加1操作

3、volatile修饰的变量值value

保证了多线程之间的内存可见性

操作时,需要比较操作内存中的值,和主内存中的值进行比较

如果Value值和预期值A的比较返回false,那么就一直执行while方法,直到期望的值和内存值Value一样

底层没有用synchronized,而用CAS,是为了提高并发性,同时,也能够实现一致性,是因为每个线程进来后,进入的do while循环,然后不断的获取内存中的值,都需要判断是否为最新,然后在进行更新操作。

例子:

假设线程A和线程B同时执行修改操作

1. value原始值为10,此时,线程A和线程B各自持有一份值为10的副本,分别存储在各自的工作内存
2. 当线程A拿到value值10的时候,CPU进行切换,线程A被挂起(失去CPU执行权)
3. 线程B也拿到Value值10,线程B并没有被挂起,接着执行了CAS方法,通过比较内存值正好为10,则表示成功,接着对内存值进行修改为15,至此,线程B修改操作完成
4. 之后线程A恢复,执行CAS方法,通过比较,发现自己副本获取到的值10和主内存中的数字15不一致,说明内存值已经被其它线程抢先一步修改,进而导致A线程本次修改失败,只能通过执行do while重新读取内存值后再进行CAS,因为内存值value被volatile修饰,所以其它线程对它的修改,线程A总能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。

这里涉及到一种自旋思想,如果操作返回false,则进行自旋直至操作返回true为止

CAS缺点

1、CPU开销大

     如果在高并发下,很多线程都需要更新一个变量,并且多次更新不成功,又因为CAS是自旋操作,反复循环,导致时间过长,消耗过大,给cpu带来大量压力,如果遇到最差的情况,某个线程一直获取到的值和预期值都不一样,这样就会无限循环


2、CAS只能保证变量级原子性,不能保证代码块的原子性

     对一个共享变量执行操作时,可以通过循环CAS的方式来保证原子操作,但是对于多个共享变量操作,循环CAS就无法保证操作的原子性,这时只能加锁来保证原子性

3、ABA问题
     在多线程场景下CAS会出现ABA问题

   例子:

线程A,期望值为10,欲更新的值为15
线程B,期望值为10,欲更新的值为15
     
场景:
    1.线程A抢先获得CPU时间片,而线程B因为其他原因阻塞
    2.线程A取值与期望的值10比较,发现相等然后将值更新为15
    3.这个时候出现了线程C,期望值为15,欲更新的值为10,线程C取值与期望的值15比较,发现相等则将值更新为10
    4.此时线程B从阻塞中恢复过来,并且获得了CPU时间片,这时候线程B取值与期望的值10比较,发现相等则将值更新为15
    虽然线程B也完成了操作,但是线程B并不知道这个值已经经过了10->15->10的变化过程。

    解决方法:
    在变量前面加上版本号,每次变量更新的时候变量的版本号都+1,即A->B->A就变成了1A->2B->3A

总结:

CAS(compareAndSwap)

通过比较当前工作内存值和主物理内存值是否相等,如果返回true,则执行规定操作,返回false,则继续自旋比较直到主内存和工作内存的值一致为止

猜你喜欢

转载自blog.csdn.net/weixin_43562937/article/details/107180533