无锁并发:探秘CAS机制的魔力

news2024/11/25 19:37:38
😊 @ 作者: 一恍过去
💖 @ 主页: https://blog.csdn.net/zhuocailing3390
🎊 @ 社区: Java技术栈交流
🎉 @ 主题: 无锁并发:探秘CAS机制的魔力
⏱️ @ 创作时间: 2023年08月28日

在这里插入图片描述

目录

  • 1、概念
  • 2、原理
  • 3、缺点
  • 4、ABA问题
  • 5、解决ABA问题

1、概念

CAS(Compare And Swap): 比较并替换,它是一条CPU原语,是一条原子指令(原子性)。
CAS通过比较真实值与预期值是否相同,如果是则进行修改,Atomic原子类底层就是使用了CAS,CAS属于乐观锁。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么会自动将该内存位置的值更新为新值,反之不做任何操作。

2、原理

CAS执行依赖于Unsafe类,Unsafe类的所有方法通过native修饰,所以Unsafe类直接操作系统底层的数据地址,而不通过JVM实现。
比如:A、B线程通过AtomicInteger同时对变量进行自增

    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

	// 使用了unsafe类
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            // 获取原值
            var5 = this.getIntVolatile(var1, var2);
            // 将原值与预期值进行对比,一致则进行相加var4
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

伪代码流程解释:
A线程将变量num从1进行自增,当执行时没有其他线程修改过num值,所以一次成功,num自增后将值返回给主内存。
B线程也将num进行自增,但是num已经被A线程进行了修改,所有将再次执行do-while,重新获取num的值。
由于JMM模型的可见性,B线程重新获取值时会从主内存中获取到被A修改后的最新值。
通过最新值与期望值进行比较,满足条件就返回true。

CAS如何实现:
通过Unsafe类对系统底层的数据地址进行原子性操作,对比内存地址的值和预期值是否一样,如果一样进行修改。

3、缺点

  • 1、循环时间长,增加了CPU的开销。
  • 2、只能保证一个变量的原子操作。
  • 3、会导致ABA问题。

4、ABA问题

如线程1从内存X中取出A,这时候另一个线程2也从内存X中取出A,并且线程2进行了一些操作将内存X中的值变成了B,然后线程2又将内存X中的数据变成A,这时候线程1进行CAS操作发现内存X中仍然是A,然后线程1操作成功。虽然线程1的CAS操作成功,但是整个过程就是有问题的,因为内存X中的值从A到B再到了A。

CAS执行时,将过去某时刻的值与当下时刻进行比较并替换,在这时间差中,值可能会发生多次修改,只是最终值的结果不变。

5、解决ABA问题

为了防止在值比较时,存在被修改过的可能,通过为值加上版本号的方式,在最后执行CAS时判断版本号,确保不会出现ABA问题。

通过原子引用(AtomicReference)加时间戳原子引用(AtomicStampedReference)解决ABA问题。

代码如下:

public class Test {

    private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {
        System.out.println("=========以下ABA问题产生:=========");
        // 线程 t1 模拟ABA问题的产生
        new Thread(() -> {
            // 进行一次修改,将值修改为101
            atomicReference.compareAndSet(100, 101);
            // 进行第二次修改,将值修改回100
            atomicReference.compareAndSet(101, 100);
        }, "t1").start();

        new Thread(() -> {
            // 线程t2暂停1S,保证线程t1执行完成
            try {TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}
            System.out.println(atomicReference.compareAndSet(100, 102));
            System.out.println("修改成功,修改后值为:"+atomicReference.get());
        }, "t2").start();


        try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();}
        System.out.println("=========以下ABA问题解决方式:=========");

        new Thread(() -> {
            // 获取版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println("线程名称:"+Thread.currentThread().getName()+",第一次版本号:"+stamp);
            try {TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}
            // 进行一次修改,将值修改为101
            atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
            stamp = atomicStampedReference.getStamp();
            System.out.println("线程名称:"+Thread.currentThread().getName()+",第二次版本号:"+stamp);
            // 进行第二次修改,将值修改回100
            atomicStampedReference.compareAndSet(101,100,stamp,stamp+1);
            stamp = atomicStampedReference.getStamp();
            System.out.println("线程名称:"+Thread.currentThread().getName()+",第三次版本号:"+stamp);
        }, "t3").start();

        new Thread(() -> {

            // 获取版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println("线程名称:"+Thread.currentThread().getName()+",第一次版本号:"+stamp);
            // 线程t4暂停1S,保证线程t3执行完成
            try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();}
            boolean andSet = atomicStampedReference.compareAndSet(100, 101, stamp,stamp+1);
            System.out.println("线程名称:"+Thread.currentThread().getName()+",修改结果:"+andSet+",第二次版本号:"+stamp+1+"实际版本号:"+atomicStampedReference.getStamp());
            System.out.println("当前最新值:"+atomicStampedReference.getReference());
        }, "t4").start();
    }
}

分析:
线程t1中,将值从100修改为了101又修改回了100,但是线程t2却成功更新了值,所以产生了ABA问题。
线程t3中,将值从100修改为了101又修改回了100,并且更新的版本号为3;在线程t4中,将100修改为101时,由于线程t4更新时的版本号为2,但是实际的版本号为3,所以无法更新,解决了ABA问题。

运行结果:
在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/941261.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React 生命周期新旧对比

前言 React16.4版本之后使用了新的生命周期&#xff0c;它使用了一些新的生命周期钩子&#xff08;getDerivedStateFromProps、getSnapshotBeforeUpdate&#xff09;&#xff0c;并且即将废弃老版的3个生命周期钩子&#xff08;componentWillMount、componentWillReceiveProps…

中国社科院与美国杜兰大学金融管理硕士——创造职业生涯的转折点

作为金融领域从业人员&#xff0c;时刻都在关注行业最新资讯是非常有必要的&#xff0c;只有金融业的前沿讯息&#xff0c;才能防范于未然&#xff0c;紧跟时代脚步。针对在职的你&#xff0c;如何利用业余时间让自己实现质的飞跃你&#xff1f;中国社科院与美国杜兰大学金融管…

门禁系统忘记登入密码,现在更换电脑如何迁移旧电脑门禁系统的数据

环境&#xff1a; ivms-4200 v3.10.0.6_c 问题描述&#xff1a; 门禁系统忘记登入密码,现在更换电脑如何迁移旧电脑门禁系统的数据&#xff0c;旧电脑记住密码&#xff0c;忘了密码和密保了 解决方案&#xff1a; 1.前往海康官网下载4200客户端&#xff0c;在新电脑上安装 …

树的介绍(C语言版)

前言 在数据结构中树是一种很重要的数据结构&#xff0c;很多其他的数据结构和算法都是通过树衍生出来的&#xff0c;比如&#xff1a;堆&#xff0c;AVL树&#xff0c;红黑色等本质上都是一棵树&#xff0c;他们只是树的一种特殊结构&#xff0c;还有其他比如linux系统的文件系…

building and deploying a single-Master RocketMQ cluster

building and deploying a single-Master RocketMQ cluster 1 、下载RocketMQ安装包(这里是通过源码安装)2、安装3、启动nameserver4、启动borkerStart the broker serviceVerify that the broker service is started successfully, for example, the brokers ip is 192.168.1.…

使用ChatGPT一键生成思维导图

指令1&#xff1a;接下来你回复的所有内容&#xff0c;都放到Markdown代码框中。 指令2&#xff1a;作为一个Docker专家&#xff0c;为我编写一个详细全面的Docker学习大纲&#xff0c;包括基础知识、进阶知识、项目实践案例&#xff0c;学习书籍推荐、学习网站推荐等&#xf…

13、Vue3 大事件管理系统

一、大事件项目介绍 和 创建 1.1 Vue3 大事件管理系统 在线演示&#xff1a; https://fe-bigevent-web.itheima.net/login 接口文档: https://apifox.com/apidoc/shared-26c67aee-0233-4d23-aab7-08448fdf95ff/api-93850835 基地址&#xff1a; http://big-event-vue-api-t.i…

技术面:ts是如何编译成js,js又如何在浏览器运行的?

文章目录 ts是如何编译成jsjs又如何在浏览器运行 TypeScript 代码需要通过【编译器】将其编译成 JavaScript 代码&#xff0c;而 JavaScript 代码则需要在浏览器中下载、解析和执行&#xff0c;才能最终呈现出页面内容&#xff0c;并与用户进行交互 ts是如何编译成js 摘抄&…

Tuxera NTFS for Mac2023苹果电脑Mac硬盘读写工具

Tuxera NTFS for Mac是一款高效稳定的NTFS读写工具&#xff0c;可以让你在Mac上完整地读写兼容NTFS格式驱动器&#xff0c;对磁盘进行访问、编辑、存储和传输文件等操作。Tuxera NTFS for Mac软件是一款高效稳定的NTFS读写工具&#xff0c;可以让你在Mac上完整地读写兼容NTFS格…

QT 消息对话框按钮显示

前言 搞QT嘛&#xff0c;大多数都是军工。都要国产化&#xff0c;而且消息对话框的按钮的英文也不是很得劲&#xff0c;所以需要汉化。使用静态函数的按钮就是显示英文&#xff0c;汉化的代码如下。 void Widget::on_pushButton_clicked() {QMessageBox box(QMessageBox::Inf…

腾讯云coding平台平台inda目录遍历漏洞复现

前言 其实就是一个python的库可以遍历到&#xff0c;并不能遍历到别的路径下&#xff0c;后续可利用性不大&#xff0c;并且目前这个平台私有部署量不多&#xff0c;大多都是用腾讯云在线部署的。 CODING DevOps 是面向软件研发团队的一站式研发协作管理平台&#xff0c;提供…

Matlab(基本操作与矩阵输入)

目录 1.Matlab视窗详读 2.基本操作与矩阵输入 2.1 运算符的优先级 2.2 初等数学函数 2.3 嵌入函数 2.4 特殊变量和常量 2.5 Matlab的优先级调用 2.6 数字显示格式长 2.7 命令行中端 2.8 部分函数 2.9 向量和矩阵 2.10 数组索引 2.11 串联矩阵 2.12 生成数值序列 …

合宙Air724UG LuatOS-Air LVGL API控件--进度条 (Bar)

进度条 (Bar) Bar 是进度条&#xff0c;可以用来显示数值&#xff0c;加载进度。 示例代码 – 创建进度条 bar lvgl.bar_create(lvgl.scr_act(), nil) – 设置尺寸 lvgl.obj_set_size(bar, 200, 20); – 设置位置居中 lvgl.obj_align(bar, NULL, lvgl.ALIGN_CENTER, 0, 0) …

9.4 集成功率放大电路

OTL、OCL 和 BTL 电路均有各种不同输出功率和不同电压增益的集成电路。应当注意&#xff0c;在使用 OTL 电路时&#xff0c;需外接输出电容。为了改善频率特性&#xff0c;减小非线性失真&#xff0c;很多电路内部还引入深度负反馈。这里以低频功放为例。 一、集成功率放大电路…

vue项目,如何修改Element-Plus等UI组件库的样式,三种方式搞定!!!

前言 我们在学习和使用组件库构建页面的时候&#xff0c;时常会遇到这样的问题。 即&#xff0c;尽管组件库已经提供了较多的功能&#xff0c;来帮助我们构建自定义的效果&#xff0c;但有时仍不能使我们满意。 这个时候我们就不得不修改UI库的样式&#xff0c;来达到想要的状…

修改类属性后出现错误 Filtered request failed. 和 unable to find class for code 253

问题描述&#xff1a; 类增加字段后项目无法启动。 javax.servlet.ServletException: Filtered request failed. Caused by: java.lang.RuntimeException: unable to find class for code 253 原因分析&#xff1a; 由于修改的User类是设置了序列化的&#xff0c;在未修改…

RISC-V 中国峰会 | OpenMPL引人注目,RISC-V Summit China 2023圆满落幕

RISC-V中国峰会圆满落幕 2023年8月25日&#xff0c;为期三天的RISC-V中国峰会&#xff08;RISC-V Summit China 2023&#xff09;圆满落幕。本届峰会以“RISC-V生态共建”为主题&#xff0c;结合当下全球新形势&#xff0c;把握全球新时机&#xff0c;呈现RISC-V全球新观点、新…

【C语言进阶(8)】自定义数据类型1:结构体

文章目录 前言Ⅰ 结构体的声明和定义⒈结构体声明⒉结构体定义⒊特殊的声明 Ⅱ 结构体的自引用Ⅲ 结构体初始化Ⅳ 访问结构体成员Ⅴ 结构体内存对齐⒈结构体内存对齐规则⒉分析结构体大小⒊嵌套结构体内存大小⒋内存对齐存在的原因 Ⅵ 修改默认对齐数Ⅶ 结构体传参 前言 C 语言…

嵌入式系统存储体系

一、存储系统概述 主要分为三种&#xff1a;高速缓存&#xff08;cache&#xff09;、主存和外存。 二、高速缓存Cache 高速缓冲存储器中存放的是当前使用得最多得程序代码和数据&#xff0c;即主存中部分内容的副本&#xff0c;其本身无自己的地址空间。在嵌入式系统中Cac…

视频怎么变成动态gif图?一个方法轻松转换

怎么将视频转换成gif动态图片呢&#xff1f;大家在日常看电影、电视剧&#xff0c;刷短视频的时候想要将其做成gif表情包时&#xff0c;应该如何操作呢&#xff1f;这时候&#xff0c;给大家分享一款操作简单无需下载的视频gif转换&#xff08;https://www.gif.cn/&#xff09;…