• CAS机制


    1、什么是CAS?

    CAS的全称是 Compare And Swap(比较再交换,确切一点称之为:比较并且相同再做交换)
    是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。CAS的作用是:CAS可以将比较和交换转换为原子操作,这个原子操作直接由处理器CPU保证。

    (可以看做是一个轻量级的synchronized,它能保证变量修改的原子操作)

    CAS指令需要有三个操作数,分别是:

    • 内存位置(在Java中可以简单地理解为变量的内存地址,用V表示)
    • 旧的预取值(用A表示)
    • 准备设置的新值(用B表示)
      在这里插入图片描述
      CAS指令执行时,当且仅当 V 符合 A 时,处理器才会用 B 更新 V 的值,否则它就不执行更新 或 重来(当他重来重试的这种行为称为——自旋)。但是不管是否更新了 V 的值,都会返回 V 的旧值。该过程是一个原子操作,执行期间不会被其他线程中断。

    它是一种CPU并发原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

    说了这么多原理,撸一下Demo吧~通过实现类AtomicInteger来演示一下CAS:

    public class CASDemo {
        public static void main(String[] args) {
            AtomicInteger atomicInteger = new AtomicInteger(6);
    
            System.out.println(atomicInteger.compareAndSet(6, 2022) + "\t" + atomicInteger.get());
            System.out.println(atomicInteger.compareAndSet(6, 2022) + "\t" + atomicInteger.get());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    第一次C的值等于A,故将C改为B。第二次C得值不等于A,故不修改。

    2、CAS实现原子操作的3大问题?

    ABA问题、循环时间长消耗资源大、只能保证一个 共享变量的原子操作。
    这里主要就讲解一下ABA问题~

    问:如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然为 A 值,那就能说明它的值没有被其他线程改变过了吗?

    答:这是不可能的,因为 如果在这段期间它的值曾经被改成 B,后来又被改回为 A,那CAS操作就会误人误它从来没有被改变过。这个漏洞称为 CAS 操作的 “ABA问题”。

    3、Unsafe类

    从 JDK5 之后,Java类库中才开始使用CAS操作,该操作由 sun.misc.Unsafe 类里面的 compareAndSwapXXX()方法包底层实现即为CPU指令cmpxchg。

    执行 cmpxchg 指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功会执行CAS操作,也就是说CAS的原子性实际上是CPU实现独占的。

    Unsafe类详解

    在这里插入图片描述

    1、Unsafe

    ​ 是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。

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

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

    3、变量value用volatile修饰,保证了多线程之间的内存可见性。

    源码解读

    接下来我们来分析一下源代码:

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    
    • 1
    • 2
    • 3

    通过点进compareAndSet() 方法的源代码,发现其是调用了unsafe.compareAndSwapInt()方法,在unsafe类中,主要有以下三个方法:

    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
    
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    
    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面三个方法都是类似的,主要对4个参数做一下说明:

    • var1:表示要操作的对象
    • var2:表示要操作对象中属性地址的偏移量
    • var4:表示需要修改数据的期望的值
    • var5/var6:表示需要修改为的新值

    这里对偏移量做一下讲解,大学汇编里有说过~this 相当于当前对象的首地址,需要找到对应的value在内存中的存放位置,此时就需要一个偏移量,即:收地址+偏移量=值在内存中的位置

    我们已知i++在多线程情况下是不安全的,那 atomicInteger.getAndIncrement() 方法呢?

    在这里插入图片描述

    假设线程A和现场B两个线程同时执行 getAndIncrement()方法(分别跑在不同CPU上):

    1. 假设主内存中 value原始值为3,根据JMM模型,线程A 和 线程B各自持有一份值为3的value的副本分别到各自的工作内存。
    2. 线程A通过getIntVolatile(var1, var2)拿到value值3,假设这时线程A被挂起。
    3. 线程B也通过getIntVolatile(var1, var2)拿到value值3,此时线程B没有被挂起并执行 compareAndSwapInt 方法,比较内存值也为3,则成功修改内存值为4,线程B执行完毕。
    4. 此时线程A被唤醒,执行compareAndSwapInt 方法比较,发现主内存中的值和旧的预期值不一致,说明该值已经被其他线程更新了,则线程A本次修改失败,自旋重来一次。
    5. 线程A重新获取value值,因为变量value被volatile修饰,所以其他线程对它的修改,线程A是可见的,线程A继续执行 compareAndSwapInt 进行比较替换,直到成功为止。

    Unsafe类中的compareAndSwapInt,对应着本地方法,该方法的实现位于unsafe.cpp,让我们一探究竟~

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xilwh0X8-1656433185456)(JUC并发编程.assets/image-20220628234302944.png)]

    4、AtomicReference<V>

    1. AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。

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

    即 可以原子更新的对象引用。

    首先编写一个User类:

    class User{
        String username;
        int age;
    		// 省略全参构造方法、setter、getter、toString
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    通过AtomicReference类实现对”User对象”进行原子操作。

    public class AtomicReferenceDemo {
        public static void main(String[] args) {
            AtomicReference<User> userAtomicReference = new AtomicReference<>();
            User hgw = new User("hgw", 22);
            User hly = new User("hly", 22);
            userAtomicReference.set(hly);
            System.out.println(userAtomicReference.compareAndSet(hly, hgw) +"\t,"+ userAtomicReference.get());
            System.out.println(userAtomicReference.compareAndSet(hly, hgw) +"\t,"+ userAtomicReference.get());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    InnoDB的逻辑存储结构是什么,表空间组成包括哪些?
    springboot入门
    网络工程师回顾学习
    一篇图解Linux内存碎片整理
    Vue3 中有场景是 reactive 能做而 ref 做不了的吗?
    【苹果家庭推送】这是iPhone上SMS功能的严重安全漏洞
    [Java Framework] [Spring] Spring中RestTemplate中几种常见的请求方式
    仿美团外卖微信小程序源码/美团外卖优惠券领劵小程序-自带流量主模式
    设计模式 -- 建造者模式
    Python使用turtle绘制简单图形-设置绝对坐标setpos(), 抬起画笔penup(),放下画笔pendown()
  • 原文地址:https://blog.csdn.net/m0_49183244/article/details/125512234