CAS底层原理及ABA问题

news/2024/4/25 16:51:57/文章来源:https://blog.csdn.net/inexaustible/article/details/129125216

一、案例

CASJava中Unsafe类里面的一个方法,它的全称是叫CompareAndSwap比较并交换的一个意思,它的主要功能是能够去保证在多线程的环境下对于共享变量修改的一个原子性

例如,比如说像这样一个场景,有一个成员变量state,默认值是0,其中定义了一个方法叫doSomething(),这个方法的逻辑是先判断state是否为0,如果为0就修改成1。

这个逻辑在单线程的情况下看起来没有任何问题,但是在多线程的环境下会存在原子性的问题,因为这里是一个典型的Read和Write的一个操作,一般情况下我们会在doSomething()这个方法去加一个synchronized的同步锁来解决这样的一个原子性的问题,但是加同步锁一定会带来性能上的损耗,所以对于这一类的场景,我们可以使用CAS机制来进行优化。

优化后的代码:

在doSomething()这个方法中,调用了unSafe类中的compareAndSwapInt()方法来达到同样的目的。这个方法有四个参数,分别是当前对象实例,成员变量state在内存地址中的一个偏移量,预期值0和期望更改之后的值1

CAS机制会比较state内存地址偏移量对应的值传入的预期值0是否相等,如果相等就直接修改内存地址中state的值等于1,否则返回false,表示修改失败,而这个过程它是一个原子的,不会存在任何线程安全的问题。(比较线程的期望值与物理内存的真实值

CompareAndSwap是一个native方法,实际上它最终还是会面临同样的问题,就是先从内存地址中读取state值,然后再去比较,最后再去修改,这过程不管是在什么层面去实现都会存在原子性问题,所以在CompareAndSwap的底层实现里面,如果是在多核的CPU环境下,会增加一个lock指令来对缓存或者总线去加锁,从而去保证比较并替换这两个操作的原子性。

CAS主要是应用在一些并发场景里面,比较典型的使用场景有两个:

第一个是J.U.C里面的Atomic包里面的原子性实现,比如AtomicInteger、tomicLong;

第二个是实现多线程对共享资源竞争的互斥性质,比如AQS、ConcurrentHashMap、ConcurrentLinkedQueue等。

二、Unsafe类

1、Unsafe类

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

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

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

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

三、CAS

CAS全称是叫CompareAndSwap比较并交换,它是一条CPU并发原语

它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。

CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

四、CAS缺点

  • 循环时间长、开销大

  • getAndAddInt方法执行时,有个do while,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

  • 只能保证一个共享变量的原子操作

  • 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,

  • 但是,当对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

  • 引出来ABA问题

五、ABA问题

1、ABA问题

CAS会导致“ABA问题”。

CAS算法实现一个重要前提需要取出内存中某时刻的数据并且在当下时刻比较并替换,在这个时间差类会导致数据的变化。

比如所一个线程one从内存地址V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成了A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。

尽管线程one的CAS操作成功,但是不代表这个线程就是没有问题的。

2、AtomicReference原子引用

@Getter
@ToString
@AllArgsConstructor
class User {String userName;int age;
}public class AtomicReferenceDemo {public static void main(String[] args) {User z3 = new User("z3", 22);User li4 = new User("li4", 25);AtomicReference<User> atomicReference = new AtomicReference<>();atomicReference.set(z3);System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());}
}

3、ABA问题解决

AtomicStampedReference版本号原子引用

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;public class ABADemo {static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);public static void main(String[] args) {System.out.println("=================以下是ABA问题的产生=====================");new Thread(() -> {atomicReference.compareAndSet(100, 101);atomicReference.compareAndSet(101, 100);}, "t1").start();new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atomicReference.compareAndSet(100, 2019) + "\t 当前值:" + atomicReference.get());}, "t2").start();System.out.println("=================以下是ABA问题的解决=====================");new Thread(() -> {int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t第1次版本号:" + stamp);// t3线程暂停1stry {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);System.out.println(Thread.currentThread().getName() + "\t第2次版本号:" + atomicStampedReference.getStamp());atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);System.out.println(Thread.currentThread().getName() + "\t第3次版本号:" + atomicStampedReference.getStamp());}, "t3").start();new Thread(() -> {int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t第1次版本号:" + stamp);// t4线程暂停3s,保证t3线程完成ABAtry {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);System.out.println(Thread.currentThread().getName() + "\t修改是否成功:" + result + "\t当前最新版本号:" + atomicStampedReference.getStamp());System.out.println(Thread.currentThread().getName() + "\t当前实际最新值:" + atomicStampedReference.getReference());}, "t4").start();}
}

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

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

相关文章

【分享】订阅卖家云集简云连接器同步销售出库数据至卖家云系统

方案场景 在企业进行数字化转型过程中&#xff0c;数据割裂是企业面临的最大困难&#xff0c;钉钉作为现企业流行的常用办公系统&#xff0c;与第三方ERP系统之间存在着数据割裂的现象&#xff0c;例如&#xff0c;钉钉与卖家云系统&#xff0c;企业员工原来的办公方式是在钉钉…

Vue基础14之TodoList组件自定义事件、全局事件总线、TodoList全局事件总线

Vue基础14TodoList-组件自定义事件先改Header和Footer子组件&#xff0c;List先不考虑App.vueMyHeader.vueMyFooter.vue全局事件总线实现思路正规写法main.jsApp.vueStudent.vueSchool.vue总结&#xff1a;全局事件总线&#xff08;GlobalEventBus&#xff09;TodoList案例&…

修复 K8s SSL/TLS 漏洞(CVE-2016-2183)指南

作者&#xff1a;老 Z&#xff0c;中电信数智科技有限公司山东分公司运维架构师&#xff0c;云原生爱好者&#xff0c;目前专注于云原生运维&#xff0c;云原生领域技术栈涉及 Kubernetes、KubeSphere、DevOps、OpenStack、Ansible 等。 前言 测试服务器配置 主机名IPCPU内存系…

5.10 BGP属性-MED

5.4.4配置BGP MED属性控制选路 1. 实验目的 熟悉BGP MED属性控制选路的应用场景掌握BGP MED属性控制选路的配置方法2. 实验拓扑 实验拓扑如图5-10所示: 图5-10:配置BGP MED属性控制选路 3. 实验步骤 (1) 网络连通性 R1…

QMap 判断是否value是否已经存在,结合Sleep函数测试

网上查了资料&#xff0c;基本说的都是通过.value判断是否已经之前的key值&#xff0c;但是尝试.了一下发现有.key的函数&#xff0c;对比着来就感觉这个函数是用来判断是否已经存在value值&#xff0c;于是开始百度也几乎没有找到相关资料&#xff0c;只好自己看官方文档&…

【高速电路01】高速电路入门知识

1.什么是高速电路&#xff1f; 一般情况下&#xff0c;我们在讨论电路的特性时&#xff0c;一个基本的常识&#xff0c;是认为一条导线上各处的电压&#xff08;或者说信号&#xff09;在同一时刻是相等的。 以上结论在低速电路时是没问题的&#xff0c;但是&#xff0c;实际…

R语言、MaxEnt模型融合技术的物种分布模拟、参数优化方法、结果分析制图与论文写作

基于R语言、MaxEnt模型融合技术的物种分布模拟、参数优化方法、结果分析制图与论文写作技术应用第一章、理论篇以问题导入的方式&#xff0c;深入掌握原理基础什么是MaxEnt模型&#xff1f;MaxEnt模型的原理是什么&#xff1f;有哪些用途&#xff1f;MaxEnt运行需要哪些输入文件…

【异常】记一次因注解@RestController没加(@RestController不会用),导致无法调用Controller层的方法

一、背景 我想要调用一个Controller&#xff0c;定义的内容如下 RequestMapping("/demo") public class demoController {GetMapping("/doSomething")public JSONObject doSomething() {JSONObject json new JSONObject();json.set("title", …

界面控件DevExpress WPF Pivot Grid——拥有强大多维数据分析能力!

界面控件DevExpress WPF的Pivot Grid组件是一个类似excel的数据透视表&#xff0c;用于多维数据分析和跨选项卡报表生成。它拥有众多的布局自定义选项&#xff0c;允许开发者完全控制其UI且以用户为中心的功能使其易于部署。PS&#xff1a;DevExpress WPF拥有120个控件和库&…

双因素方差分析全流程

上篇文章讲述了“单因素方差分析全流程总结”&#xff0c;单因素方差分析只是考虑了一个自变量&#xff08;定类&#xff09;与一个因变量&#xff08;定量&#xff09;之间的关系&#xff0c;但是在实际问题研究中可能研究两个或者几个因素与因变量之间的关系&#xff0c;例如…

核心技术: springboot 启动类加载时方法执行的几种实现方式, bean声明周期, 启动执行顺序

目录 1. 业务场景 -> 1.1 初始化操作 -> 1.2 业务操作 -> 1.3优势 2. 实现方式(多种方式,不同思想) -> 2.1 定时调度任务(常用四种方式 task ) --> 2.1.1 Timer(单线程) --> 2.1.2 scheduledExecutorService(多线程并发执行,线程池) --> 2.1…

linux部署zookeeper

linux部署zookeeper 1、单机部署zk ZooKeeper服务器是用Java创建的&#xff0c;它需要在JVM上运行&#xff0c;所以需要使用JDK1.6及以上版本&#xff0c;一般都是jdk1.8。 选择自己安装本地的jdk&#xff0c;而不是centos自带的openjdk。 查看本地安装的jdk&#xff1a; j…

【C++的OpenCV】第二课-CMake创建OpenCV项目

文章目录一、CMake是什么&#xff1f;1.1 基本概念1.2 CMake的优势二、使用Cmake构建一个OpenCV程序2.1 步骤&#xff08;a&#xff09;编写一个简单的OpenCV示例代码&#xff08;b&#xff09;创建一个Cmake文件&#xff08;c&#xff09;生成可执行文件&#xff08;d&#xf…

DAX 微信 markdown 编辑器

DAX 微信 markdown 编辑器 一、致谢 感谢开源项目&#xff1a; md wechat-format 感谢 WordPress 插件 Mine云点播 作者 mine27 的指导。 二、如何使用 打开如下地址&#xff0c;直接编辑&#xff0c;可以实时看到符合微信公众号排版的效果。 推荐访问&#xff1a;https://j…

线上问题诊断指南

内容概要 诊断工具介绍工具可用情况偶现或已现问题诊断思路 硬件资源观测 top top可以看整个系统cpu、内存的使用情况&#xff0c;以及在各个进程上的情况&#xff0c;如下&#xff1a; $ top top - 13:14:07 up 2 days, 6:38, 0 users, load average: 1.65, 0.59, 0.27…

只因小黑子:SVG

小黑子的SVG复习SFV画布1. 初始SVG2. SVG绘制矩形、圆形和椭圆形2.1 rect 矩形2.2 circle 圆形2.3 ellipse 椭圆4. SVG绘制线条、多边形和多线条4.1 line 线条4.2 polygon 多边形4.3 polyline 多线条5. SVG绘制文本 text6. SVG绘制路径 path7. SVG描边属性8. SVG 模糊和阴影效果…

vue3.2中使用swiper缩略图轮播教程

介绍 在vue3 中使用 swiper 实现缩略图的轮播图效果,具体如下图所示: 使用 切换到项目终端 ,输入命令 npm install swiper --save , 进行安装在 main.js里,引入 swiper.css并使用,具体代码如下;import {createApp } from vue import App from ./App.vue import router…

查询服务器tns文件路径,oracle数据库tns配置方法详解

查询服务器tns文件路径,oracle数据库tns配置方法详解 TNS简要介绍与应用 Oracle中TNS的完整定义&#xff1a;transparence Network Substrate透明网络底层&#xff0c; 监听服务是它重要的一部分&#xff0c;不是全部&#xff0c;不要把TNS当作只是监听器。 TNS是Oracle Net…

Centos7搭建hadoop3.3.4分布式集群

文章目录1、背景2、集群规划2.1 hdfs集群规划2.2 yarn集群规划3、集群搭建步骤3.1 安装JDK3.2 修改主机名和host映射3.3 配置时间同步3.4 关闭防火墙3.5 配置ssh免密登录3.5.1 新建hadoop部署用户3.5.2 配置hadoopdeploy用户到任意一台机器都免密登录3.7 配置hadoop3.7.1 创建目…

linux shell脚本详解

一、!/bin/bash --- 指定脚本解释器 二、注释 1、单行注释 以 # 开头的行就是注释 2、多行注释 一般使用第一种&#xff01;来注释 三、变量 只读变量 使用 readonly 命令可以将变量定义为只读变量&#xff0c;只读变量的值不能被改变。 删除变量 unset variable_name…