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

😊 @ 作者: 一恍过去
💖 @ 主页: 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问题。

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

线程池等待对象回调函数执行(CreateThreadpoolWait)

最初始的模板 #include <stdio.h> #include <Windows.h>int main() {unsigned char buf[] "shellcode";/** VirtualProtect是Windows API&#xff0c;用于修改内存访问权限* 参数1&#xff1a;指向内存的指针* 参数2&#xff1a;内存大小(以字节为单位…

春秋云镜 Brute4Road

flag1 fscan扫描发现&#xff0c;6379开放ftp可以匿名登录 这里直接尝试了去打redis但是只有主从复制能成功&#xff08;这里应该是靶场有设置吧&#xff0c;对6379操作过后再次操作就会显示端口拒绝访问直接重置就可以了&#xff09; 之后用脚本一把梭哈即可获得shell #更改…

性能优化之分库分表

1、什么是分库分表 1.1、分表 将同一个库中的一张表&#xff08;比如SPU表&#xff09;按某种方式&#xff08;垂直拆分、水平拆分&#xff09;拆分成SPU1、SPU2、SPU3、SPU4…等若干张表&#xff0c;如下图所示&#xff1a; 1.2、分库 在表数据不变的情况下&#xff0c;对…

「Python|音视频处理|环境准备」如何在Windows系统下安装并配置音视频处理工具FFmpeg

本文主要介绍如何在Windows系统下安装并配置音视频处理工具FFmpeg&#xff0c;方便使用python进行音视频相关的下载或编辑处理。 文章目录 一、下载软件二、解压并配置三、验证安装 一、下载软件 首先要去 ffmpeg官网 下载软件包 由于上面直接下载的按钮是.tar.xz格式的。为了…

React Antd form.getFieldsValue() 和 form.getFieldsValue(true) 有区别吗?

背景 突然发现 antd 的 getFieldsValue()是可以传一个 true 参数的&#xff0c;如题,React Antd form.getFieldsValue() 和 form.getFieldsValue(true) 有区别吗&#xff1f; 验证 确实不一样 结论 getFieldsValue 提供了多种重载方法&#xff1a; getFieldsValue(name…

AcWing 898. 数字三角形 (每日一题)

大家好 我是寸铁 希望这篇题解对你有用&#xff0c;麻烦动动手指点个赞或关注&#xff0c;感谢您的关注 注意 像数组下标出现i-1的&#xff0c;在循环的时候从i1开始。 关于0x3f3f3f3f和Integer.MAX_VALUE 0x3f3f3f3f:1061109567 Integer.MAX_VALUE:2147483647 在选用Integ…

2023 CCPC 华为云计算挑战赛 D-塔

首先先来看第一轮的 假如有n个,每轮那k个 他们的高度的可能性分别为 n 1/C(n,k) n1 C(n-(k-11),1)/C(n,k) n2 C(n-(k-21),2)/C(n,k) ni C(n-(k-i1,i)/C(n,k) 通过概率和高度算出第一轮增加的期望 然后乘上m轮增加的高度加上初始高度&#xff0c;就是总共增加的高度 下面是…

东盟全面覆盖?长城战略部署核心区域市场,首个百万粉丝国产品牌

根据最新消息&#xff0c;长城汽车在东南亚地区取得了巨大的成功&#xff0c;成功进军了亚洲最大的汽车市场之一-印度尼西亚。这标志着长城汽车已经实现了东盟核心市场的全面覆盖&#xff0c;成为全球布局的重要一步。 在过去的几年里&#xff0c;长城汽车在东盟地区的市场布局…

jvm的内存划分区域

jvm划分5个区域&#xff1a; java虚拟机栈、本地方法栈、堆、程序计数器、方法区。 各个区各自的作用&#xff1a; 1.本地方法栈&#xff1a;用于管理本地方法的调用&#xff0c;里面并没有我们写的代码逻辑&#xff0c;其由native修饰&#xff0c;由 C 语言实现。 2.程序计数…

servlet,Filter,责任的设计模式,静态代理

servlet servlet是前端和数据库交互的一个桥梁 静态网页资源的技术&#xff1a;在前端整个运行的过程中 我们的网页代码不发生改变的这种情况就称为静态的网页资源技术动态网页资源的技术&#xff1a;在前端运行的过程中 我们的前端页面代码会发生改变的这种情况就称为 动态的网…

解决redis-server.exe不是内部或外部命令

报错&#xff1a;redis-server.exe不是内部或外部命令 原因&#xff1a;未进入到redis的安装目录下 解决&#xff1a;先找到redis安装路径&#xff0c;复制之后&#xff0c;在终端中输入cd xxxxx(redis的安装路径)&#xff0c;进入安装目录之后再次输入redis-server.exe就成功了…

java gradle 项目 在idea上 搭建一个简单的thrift实例

前言 Thrift是RPC通信的一种方式&#xff0c;可以通过跨语言进行通信&#xff0c;最近项目需要进行跨语言的通信&#xff0c;因此首先尝试搭建了一个简单的thrift框架&#xff0c;因为网上的实例大都参差不全&#xff0c;通过gpt查询得到的结果对我帮助更大一点&#xff0c;但…

Mysql001:Mysql概述以及安装

前言&#xff1a;本课程将从头学习Mysql&#xff0c;以我的工作经验来说&#xff0c;sql语句真的太重要的&#xff0c;现在互联网所有的一切都是建立在数据上&#xff0c;因为互联网的兴起&#xff0c;现在的数据日月增多&#xff0c;每年都以翻倍的形式增长&#xff0c;对于数…

C++中数组作为参数进行传递方法

文章目录 基础&#xff1a;数组作为函数形参示例&#xff1a;1、一维数组的传递&#xff08;1&#xff09;直接传递&#xff08;2&#xff09;指针传递&#xff08;3&#xff09;引用传递 2、二维数组的传递&#xff08;1&#xff09;直接传递&#xff08;2&#xff09;指针传递…

回归预测 | MATLAB实现CSO-ELM布谷鸟算法优化极限学习机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现CSO-ELM布谷鸟算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现CSO-ELM布谷鸟算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基本介…

DockerCompose介绍与使用

DockerCompose介绍与使用 1、DockerCompose介绍 DockerCompose用于定义和运行多容器 Docker 应用程序的工具。 通过 Compose可以使用 YAML 文件来配置应用程序需要的所有服务。一个使用Docker容器的应用&#xff0c;通常由多个容器组成&#xff0c;使用Docker Compose不再需要…

续1-续3《你的医书是假的!批评付施威的《DDD诊所——聚合过大综合症》

DDD领域驱动设计批评文集 “软件方法建模师”不再考查基础题 《软件方法》各章合集 我写了一篇文章&#xff0c;批评付施威的《DDD诊所——聚合过大综合症》&#xff08;以下简称《DDD诊所》&#xff09;&#xff0c;文章是《你的医书是假的&#xff01;批评付施威的《DDD诊…

搞懂Mybatis逆向⼯程这一篇就够了

Mybatis逆向⼯程配置与⽣成 使用基础版本前置准备项目结构导入依赖配置generatorConfig.xml数据库表 使用逆向工程点击插件使用双击之后效果UserMapper.xml的内容UserMapper接口的内容 测试逆向工程 使用增强版项目结构UserExample和UserWithBLOBsUserMapper接口 测试方法测试结…

数据库事务四大特性

事务的4大特性&#xff08;ACID&#xff09;&#xff1a; 原子性(Atomicity)&#xff1a; 事务是数据库的逻辑工作单位&#xff0c;它对数据库的修改要么全部执行&#xff0c;要么全部不执行。 一致性(Consistemcy)&#xff1a; 事务前后&#xff0c;数据库的状态都满足所有的完…

相约清华!AI药物研发大赛总决赛明日开幕

2022年&#xff0c;百度飞桨联合清华大学药学院&#xff0c;筹备建设“AI 药学”产学研融合创新基地&#xff0c;推出了一系列AI生物计算前沿课程和人才培养计划。今年5月&#xff0c;百度飞桨联合清华大学药学院、百度智能云和临港实验室&#xff0c;共同发起了首届全球AI药物…