• 多线程CAS机制(图解)



    前言

    博主个人社区:开发与算法学习社区

    博主个人主页:Killing Vibe的博客

    欢迎大家加入,一起交流学习~~

    上篇总结了以下多线程场景下常见锁的策略,这篇总结一下CAS机制引起的ABA问题,以及解决方式。

    一、CAS是什么?

    乐观锁的一种实现,程序并不会阻塞,只会不断重试。

    CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:
    我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

    1. 比较 A 与 V 是否相等。(比较)
    2. 如果比较相等,将 B 写入 V。(交换)
    3. 返回操作是否成功。

    二、CAS如何实现的?

    针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:

    • java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;
    • unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;
    • Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子
      性。
      简而言之,是因为硬件予以了支持,软件层面才能做到

    三、CAS的应用

    3.1 原子类

    标准库提供了 java.util.concurrent 包(juc包,并发工具包,包含原子类,线程安全集合比如ConcurrentHashMap,CopyOnWriteArrayList等等)

    举个栗子:

    比如定义一个整型变量int i = 0:

    i++ 或者 i – 这些都是非原子性的操作,多线程并发会有线程安全问题。这个时候就要使用原子类保证它的线程安全性。

    那什么是原子性呢?

    就是指该操作对应CPU的一条指令,这个操作不会被中断,要么全部执行,要么全不执行,不会存在中间状态,那么这种操作就是一个原子性操作。

    比如int a = 10 这种就是直接将常量10赋值给a变量,是一个原子性操作,要么赋值成功要么失败。

    再比如a += 10 这种就是非原子性的操作,先要读取当前a变量的值,然后到寄存器中进行a+10计算,最后将计算得出的值重新赋值给a变量(对应了三个原子性操作)

    所以我们可以使用原子类来保证线程安全性,比如常见的AtomicInteger类:

    //有参构造可以传入参数,传入多少就从多少开始计数,无参构造默认为0.
    AtomicInteger atomicInteger = new AtomicInteger(0); 
    // 相当于 i++
    atomicInteger.getAndIncrement();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    看下源码:
    在这里插入图片描述

    通过形如上述代码就可以实现一个原子类. 不需要使用重量级锁, 就可以高效的完成多线程的自增操作.

    3.2 自旋锁

    使用CAS来实现自旋锁,乐观锁的一种实现

    自旋锁指的是在获取锁失败的线程不进入阻塞态,而是在CPU上空转(线程不让出CPU,而是跑一些无用指令),不断查询当前锁的状态。

    伪代码:

    public class SpinLock {
        private Thread owner = null;
        public void lock(){
            // 通过 CAS 看当前锁是否被某个线程持有. 
            // 如果这个锁已经被别的线程持有, 那么就自旋等待. 
            // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
            while(!CAS(this.owner, null, Thread.currentThread())){
           }
       }
        public void unlock (){
            this.owner = null;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    解释一下CAS(this.owner, null, Thread.currentThread())操作:

    只有当this.owner == null , 即当前自旋锁没有被任何线程持有 =>

    就尝试将this.owner ==Thread.currentThread() ,将持有锁的线程设置为当前线程

    四、CAS引发的ABA问题

    4.1 什么是ABA问题

    有两个线程t1和t2,同时修改共享变量num,初始num == A

    正常情况下,只有一个线程会将num 改为正确值,另一个线程在修改时num != A,另一个线程的工作内存的值已经过期了,因此无法修改

    但如果线程 t2 经过多次修改后又回到了同一个值,此时线程 t1 会以为线程 t2 没被修改过,然后就去修改值 . 此时t2之间做的各种修改 其实对 t1来说都是不可见的 。

    下面是ABA问题的图解:

    在这里插入图片描述

    4.2 如何解决

    为了解决ABA问题,就要引入版本号机制。

    给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.

    • CAS 操作在读取旧值的同时, 也要读取版本号.
    • 真正修改的时候,
      如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.
      如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了).

    详情可以看下面这个链接:

    版本号机制

    总结

    以上就是多线程CAS机制的详解了,后续博主会更新Synchronized 原理 和JUC的常见类及用法,欢迎关注,点赞+收藏~~~

  • 相关阅读:
    界面组件DevExpress WinForms v23.1亮点 - 全新升级HTML & CSS模板
    软著材料编写脚本
    y125.第七章 服务网格与治理-Istio从入门到精通 -- Sidecar及流量拦截机制和访问网格外部服务(十一)
    Qt (QFileDialog&QColorDialog&QFontDialog) 对话框实战
    PEG/蛋白Protein/抗体antibody 功能化修饰硫化锌量子点 ZnS QDs
    物联网通信技术课程作业资料(TPUNB技术)
    AI大预言模型——ChatGPT与AI绘图及论文高效写作
    排序算法集合
    【汇编语言】3.汇编语言程序
    自动驾驶中的人工智能,自动驾驶与人工驾驶
  • 原文地址:https://blog.csdn.net/qq_43575801/article/details/127786362