What’s CAS & sun.misc.Unsafe
全称 Compare-And-Swap , 主要实现的功能是和内存中的某个位置的值进行比较判断是否为预期值,如果是预期值则更改为新值, 整个过程具有原子性。
CAS & sun.misc.Unsafe
CAS属于CPU并发原语
CAS是一种系统原语,原语属于操作系统应用范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致的问题,即CAS是线程安全的
在JDK中,主要体现在sun.misc.Unsafe类。
当执行UnSafe类中的cas相关方法时, JVM会转换成类似汇编指令,通过它实现了原子操作。
来看个代码
package com.artisan.juc; import java.util.concurrent.atomic.AtomicInteger; /** * @author 小工匠 * @version 1.0 * @description: TODO * @date 2021/11/6 13:11 * @mark: show me the code , change the world */ public class CASTest { public static void main(String[] args) { // 设置初始值为100 AtomicInteger atomicInteger = new AtomicInteger(100); // 使用atomicInteger的compareAndSet,如果为100,则更新为123 boolean b = atomicInteger.compareAndSet(100, 123); System.out.println(b + "----" + atomicInteger.get()); // 使用atomicInteger的compareAndSet,如果为100,则更新为456 (上一步已经更新成了123,所以不是100) b = atomicInteger.compareAndSet(100, 456); System.out.println(b + "----" + atomicInteger.get()); } }
输出
true----123 false----123
以AtomicInteger为例底层原理剖析
继续在上个例子的基础上,完善一下,方便引入知识点,增加红框内如下代码:
输出
我们来分析下 getAndIncrement
先看看 AtomicInteger类
看方法的注释说明: 以原子的方式在当前值的基础上加1 ,返回的是加1之前的值。
可以看到其实是调用了unsafe# getAndAddInt
那先看看Unsafe类 呗
内部方法操作可以像C的指针一样直接操作内存
Unsafe位于sun.misc包中,该类的方法都是native的本地方法 ,这也意味着unsafe类中的方法都直接调用操作系统底层资源执行相应的任务。
Unsafe类是CAS的核心类. 我们知道Java无法直接访问底层操作系统,需要通过native方法来实现。 Unsafe这个魔法类可以理解为一个后门,通过该类可以直接操作特定的内存数据。
继续【getAndAddInt方法 】
到
到
var1: AtomicInteger本对象
var2: 该对象值得引用地址
var4: 需要变动的数量
var5: var5 = this.getIntVolatile(var1, var2); 从主内存中拿到的值 , 如果当前值和期望值一样,就执行 var5 + var4 . (用var1和var2找到的内存中的真实值用该对象当前的值与var5比较)
do while 循环 , 如果compareAndSwapInt返回false,那么就一直执行 while方法,直到期望的值和真实值一样
CAS有3个操作数,内存值V,旧的预期值,要修改的更新值。当且仅当预期值和内存值相同时,将内存值修改为更新值,否则不操作 .
CAS缺点
CAS不加锁,保证一致性,但是需要多次比较
对于多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候只能用锁来保证原子性
循环时间长,开销大(因为执行的是do while,如果比较不成功一直在循环,最差的情况,就是某个线程一直取到的值和预期值都不一样,这样就会无限循环)
只能保证一个共享变量的原子操作,当对一个共享变量执行操作时,我们可以通过循环CAS的方式来保证原子操作
ABA 问题
如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。