CAS原理以及AtomicStampedRefernce

在这里插入图片描述

目录

  • CAS概念
    • 没有使用CAS之前
    • 使用CAS之后
    • CAS介绍
    • 代码案例
    • 为什么CAS能保证原子性?
      • 1、Unsafe
      • 2、offset 偏移量
      • 3、变量value用volatile修饰
      • 4、自旋保持原子性
      • 5、底层汇编语言的具体执行
  • 原子引用
  • CAS与自旋锁
    • 实现一个自旋锁
  • CAS缺点及解决
    • AtomicStampedRefernce

CAS概念

没有使用CAS之前

多线程不使用原子类保证线程安全i++(基本数据类型)通常要这么写
在这里插入图片描述

使用CAS之后

多线程环境,使用原子类保证线程安全i++(基本数据类型),类似于安全锁

上面的代码就可以直接演变成这样,AtomicInteger ,整数原子类
在这里插入图片描述
没有加synchronized重锁,在性能上更好,且又保证了原子性

CAS介绍

compare and swap的缩写,中文翻译成比较并交换,实现并发算法时常用到的一种技术。

它包含三个操作数–内存位置、预期原值及更新值。

执行CAS操作的时候,将内存位置的值与预期原值比较:
如果相匹配,那么处理器会自动将该位置值更新为新值
如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。

CAS有3个操作数,位置内存值V,旧的预期值A,要修改的更新值B。当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来当它重来重试的这种行为成为----自旋!!

在这里插入图片描述
为什么说有点类似乐观锁呢,我利用CAS原理举例说明:

一个数据块,里面放了一个数字5,有三个线程A、B、C带着i++任务,要对数据块的数据进行+1操作,假如线程A先到数据块,线程A自身带着一个数字5,这时候就会拿数据块的数字5跟自身数字5做比较,一看都是5,那么线程A就往数据块里面的5加1,此时数据块里面的数据就是6,线程A完成任务后走了,这时候线程B来了,他身上也带着数字5,但是数据块里面变成6,不匹配,线程B不服输,重新从数据块中拿数字(重新拿这个就是一个自旋),这时候重新拿的6跟数据块中的6一致,这时候往数据块的6+1做个任务,把7丢了进去,任务结束(线程B乐观的任务数据块的数据和自己总能保持一致,如果不一致就重新拿,再修改)

以上线程B做了两个事:比较(比较数字5)+交换(丢了个7给数据块),这就是CAS原理

代码案例

把上面的案例转化为代码,可见5数字比较是一样的,那么就返回了7
在这里插入图片描述
那如果再重复一次,数据就有不一致了,因为重复部分atomicInteger已经是数值7了,不匹配,所以false
在这里插入图片描述

为什么CAS能保证原子性?

CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性它是非阻塞的且自身具有原子性,也就是说这玩意效率更高且通过硬件保证,说明这玩意更可靠。

CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。

执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的,比起用synchronized重量级锁,这里的排他时间要短很多,所以在多线程情况下性能会比较好。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
参数说明:
o:要操作的对象
offset:对象中属性地址的偏移量
expected:需要修改数据的期望值(匹配条件的值)
x:需要修改的新值

那么就从上引出,为什么Unsafe类能实现CAS的功能呢?

1、Unsafe

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

注意unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。
在这里插入图片描述
在这里插入图片描述

2、offset 偏移量

是内存中偏移地址Unsafe是根据内存偏移地址来获取数据的
在这里插入图片描述

3、变量value用volatile修饰

保证多线程之间的内存可见性
在这里插入图片描述

4、自旋保持原子性

通过AtomicInteger…getAndIncrement()源码可以看到
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
CAS并发原语体现在java语言中就是sum.misc.Unsafe类中的各个方法,并且原语的执行必须是连续,在执行中不可被打断,所以不会造成数据不一致的问题。

5、底层汇编语言的具体执行

Jnsafe类中的compareAndSwapint,是一个本地方法,该方法的实现位于unsafe.cpp中

  1. 先想办法拿到变量value在内存中的地址,根据偏移量valueOffset,计算 value 的地址
  2. 调用 Atomic 中的函数 cmpxchg来进行比较交换,其中参数x是要交换的值 e是要比较的值
  3. cas成功,返回期望值e,等于e,此方法返回true
  4. cas失败,返回内存中的value值,不等于e,此方法返回false

JDK提供的CAS机制,在汇编层级会禁止变量两侧的指令优化,然后使用cmpxchg指令比较并更新变量值(原子性)

原子引用

那么既然CAS给我们准备了一些基础原子类去使用,我们可不可以自定义原子类呢?

jdk给我们提供了一个原子引用类AtomicReference,具体使用方法见如下

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

在这里插入图片描述
与提供的原子类是有相同方法

CAS与自旋锁

自旋锁(spinlock)
CAS 是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,至于自旋呢,看字面意思也很明白,自己旋转。是指尝试获取锁的线程不会立即阻寒,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是环会消耗CPU

CAS 是实现自旋锁的基础,自旋翻译成人话就是循环,一般是用一个无限循环实现。这样一来,一个无限循环中,执行一个CAS 操作
当操作成功返回 true 时,循环结束:
当返回 false 时,接着执行循坏,继续尝试CAS 操作,直到返回true。

实现一个自旋锁

AtomicReference<Thread> atomicReference = new AtomicReference<>();public void lock() {Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName() + "\t ---come in");while (!atomicReference.compareAndSet(null, thread)) {}}public void unlock() {Thread thread = Thread.currentThread();atomicReference.compareAndSet(thread,null);System.out.println(Thread.currentThread().getName() + "\t ---task over,unlock...");}public static void main(String[] args) {final Demo demo = new Demo();new Thread(()->{demo.lock();//暂停几秒try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();}demo.unlock();},"A").start();//让a先启动try {TimeUnit.MILLISECONDS.sleep(500);} catch (Exception e) {e.printStackTrace();}new Thread(()->{demo.lock();demo.unlock();},"B").start();}

效果图,b没有等到a结束就一直在自旋等待a
在这里插入图片描述

CAS缺点及解决

  • 循环时间开销很大(do while )
    在这里插入图片描述

  • 引出ABA问题
    例子:财务记账,A财务记账时账上有100元,这时候A离开了,B进来,挪用了50块,过了会又补充进来50块,B就走了,这时候账上依旧是100,A回来,明面上看着没问题,但是这中间就被操作了(用其他人的钱补充了进去,账面不干净了),这就是ABA问题

那怎么解决呢?版本号,戳记流水

AtomicStampedRefernce

AtomicStampedRefernce自带设置版本号的属性
在这里插入图片描述
这样子就能暂时解决ABA问题,这是单线程的,那么多线程情况下呢?

多线程问题代码示例:

 static AtomicInteger atomicInteger = new AtomicInteger(100);public static void main(String[] args) {new Thread(()->{atomicInteger.compareAndSet(100,101);try {TimeUnit.MILLISECONDS.sleep(10);} catch (Exception e) {e.printStackTrace();}atomicInteger.compareAndSet(101,100);},"A").start();new Thread(()->{try {TimeUnit.MILLISECONDS.sleep(200);} catch (Exception e) {e.printStackTrace();}System.out.println(atomicInteger.compareAndSet(100, 2024) + "\t" + atomicInteger.get());},"B").start();}

在这里插入图片描述
虽然结果是对的,但是无法预料到中间出现了什么变动

多线程情况下的使用:

static AtomicStampedReference<Integer> atomicStampedReference =new AtomicStampedReference<>(100,1);public static void main(String[] args) {new Thread(()->{final int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t 首次版本号" + stamp);//暂停一段时间,让b线程初始化的值跟我一样try {TimeUnit.MILLISECONDS.sleep(500);} catch (Exception e) {e.printStackTrace();}atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),stamp+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());},"A").start();new Thread(()->{final int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t 首次版本号" + stamp);//等待a线程出现aba问题try {TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}final boolean b = atomicStampedReference.compareAndSet(100, 2024, stamp, stamp + 1);System.out.println(b + "\t" + atomicStampedReference.getReference()+"\t "+atomicStampedReference.getStamp());},"B").start();}

在这里插入图片描述
中间有被改变的痕迹,因此b线程想要去修改时,就不让修改,解决ABA问题

就先说到这 \color{#008B8B}{ 就先说到这} 就先说到这
在下 A p o l l o \color{#008B8B}{在下Apollo} 在下Apollo
一个爱分享 J a v a 、生活的小人物, \color{#008B8B}{一个爱分享Java、生活的小人物,} 一个爱分享Java、生活的小人物,
咱们来日方长,有缘江湖再见,告辞! \color{#008B8B}{咱们来日方长,有缘江湖再见,告辞!} 咱们来日方长,有缘江湖再见,告辞!

在这里插入图片描述

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

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

相关文章

记录使用el-form的resetFields时遇到的表单数据回显失败的问题,去除nextTick解决

首先简单介绍一下resetFields的基础作用 element-plus官网el-form介绍 本案例中实现点击每行的编辑按钮时&#xff0c;弹出弹窗和表单 由于设置了表单校验&#xff0c;如图&#xff0c;表单内容不符合设定的校验规则时会有提示 如果仅仅这样就会出现问题&#xff0c;下次打…

海山数据库(He3DB)技术解析:海山Redis定时任务与持久化管控设计

文章目录 引言一、背景介绍二、具体实现1、多副本容灾功能2、主备切换后任务断点续做功能3、持久化管控编排功能 三、总结作者 引言 云Redis数据库服务是目前广泛应用的模式&#xff0c;其数据持久化方案是现在研究的热点内容&#xff0c;数据持久化操作主要由参数设置自动触发…

华为交换机Telnet配置

华为交换机Telnet配置 一、Telnet概述与作用 1、Telnet简介 Telnet&#xff08;Telnet协议&#xff09;是一种用于远程登录到计算机或网络设备的协议。它允许用户在本地计算机上通过网络连接到远程计算机或设备&#xff0c;并在远程计算机上执行命令和操作。Telnet协议最初是…

mysql特殊字符、生僻字存储设置

mysql utf-8模式下&#xff0c;分为ut8mb3,utf8mb4&#xff0c;mb4是支持特殊字符、emoji表情的&#xff0c;mb3是不支持的。 报错信息&#xff1a; 1### Error updating database. Cause: java.sql.SQLException: Incorrect string value: \xF0\xA8\x92\x82\xE6\x95... fo…

什么是公司自建企业邮箱?自建企业邮箱有什么用?

什么是公司自建企业邮箱&#xff1f;公司自建企业邮箱有什么用途&#xff1f;一是品牌统一&#xff1b;二是安全性增强&#xff1b;三是定制化功能&#xff1b;四是控制与灵活性等等。哪些企业适合自建企业邮箱呢&#xff1f;本篇文章将为您一一解释。 一、什么是公司自建企业…

Photos框架 - 自定义媒体选择器(UI列表)

引言Photos框架 - 自定义媒体资源选择器&#xff08;数据部分&#xff09;-CSDN博客 关于自定义媒体选择器上一篇博客我们已经介绍了使用Photos获取媒体资源数据和处理媒体资源数据&#xff0c;有了数据&#xff0c;UI的实现就比较灵活了&#xff0c;我就以上面的设计样式为例…

前端养成记-实现一个低配版简单版本的vue3表单自定义设计组件

简介&#xff1a; 通过使用了最新的vue3,vite2,TypeScript等主流技术开发&#xff0c;并配合使用vuedraggable 插件以及antd design vue 组件库实现低配版本的自定义表单设计组件&#xff1b; 项目地址&#xff1a;https://gitee.com/hejunqing/vue3-antdv-generator

直指谷歌?OpenAI将推出在线搜索工具“SearchGPT”

KlipC报道&#xff1a;OpenAI 也在经过一段时间的沉寂后&#xff0c;重新开始了密集的新品发布。当地时间7月25日&#xff0c;OpenAI进军搜索市场&#xff0c;宣布正在测试新的AI搜索工具SearchGPT。目前只向一小部分用户开放&#xff0c;从中获取反馈意见。 华尔街分析师表示…

Cuppa CMS v1.0 任意文件读取漏洞(CVE-2022-25578)

前言 春秋云镜靶场是一个专注于网络安全培训和实战演练的平台&#xff0c;旨在通过模拟真实的网络环境和攻击场景&#xff0c;提升用户的网络安全防护能力和实战技能。这个平台主要提供以下功能和特点&#xff1a; 实战演练&#xff1a; 提供各种网络安全攻防演练场景&#…

深层互联重磅发布,潮流开放式一对多无线导游讲解器

佩戴讲解器走进博物馆&#xff0c;已经是很多人的假期日常&#xff0c;但让我们越来越习以为常的无线讲解器&#xff0c;也许没那么简单。日前&#xff0c;深层互联全新推出IndoorLink潮流开放式导游讲解器&#xff0c;可开展主副双讲及同声传译&#xff0c;将不简单推向了一个…

【vue3|第18期】Vue-Router路由的三种传参方式

日期:2024年7月17日 作者:Commas 签名:(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释:如果您觉得有所帮助,帮忙点个赞,也可以关注我,我们一起成长;如果有不对的地方,还望各位大佬不吝赐教,谢谢^ - ^ 1.01365 = 37.7834;0.99365 = 0.0255 1.02365 = 1377.408…

上传文件传参 pc端vue的formData

formData let formData new FormData(); formData.append("file", blob, ref ".png"); //添加参数并且重新命名文件名称 if(ref.toString().indexOf(qrcode) > 0) formData.append(noStbg, true)//添加参数 uploadType(formData, sour…

保障企业数据主权:安全可控的爬虫工具与管理平台

摘要 在数据驱动的时代&#xff0c;企业对数据的需求日益增长&#xff0c;但如何在保障数据主权的前提下高效采集数据&#xff1f;本文深入探讨了选择安全可控爬虫工具与管理平台的重要性&#xff0c;分析了关键特性&#xff0c;并提出实用建议&#xff0c;助力企业维护数据安…

2024新版 黑马程序员《C++零基础入门》笔记——第一章17 数据类型-布尔型

1.数据类型-布尔型 2.代码实践 #include "iostream" using namespace std;int main() {// 布尔&#xff1a;bool 字面量仅仅有2个&#xff1a;true或falsebool flag true; // true 表示是真&#xff0c;本质上是数字1bool flag2 false; // false 表示是…

NVIDIA Drivers、CUDA、Pytorch安装

NVIDIA Drivers、CUDA、Pytorch 这三者的版本有着十分紧密的关联&#xff0c;很容易因为版本不对而重复卸载、重装。 这里写个记录&#xff0c;方便查阅。 一、NVIDIA Drivers、CUDA NVIDIA Drivers、CUDA 的关系可以在这里看到&#xff1a; CUDA Compatibilityhttps://docs.n…

7.25 阿里云OSS上传 + 后台返回token + 导出excel

1.阿里云Oss上传 只需要一点就是上传到云端后&#xff0c;前端调用上传文件接口&#xff0c;返回一个资源路径。 接着在提交表单时&#xff0c;前端把这个路径设置为img的参数即可。 1.1上传限制 只上传图片 Api("阿里云文件管理") CrossOrigin //跨域 RestContr…

C语言进阶——一文带你深入了解“C语言关键字”(中篇)

本篇文章属于C语言进阶篇的“C语言关键字”&#xff0c;旨在分享我对C语言关键字的深度学习和了解。同时带领大家深入浅出的走进C语言进阶知识——关键字篇&#xff01; 目录 一、变量的命名规则 二、标识符的命名规则 一、变量的命名规则 1、命名应当直观且可以拼读&#x…

【基础算法总结】优先级队列

优先级队列 1.最后一块石头的重量2.数据流中的第 K 大元素4.前K个高频单词4.数据流的中位数 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1…

汉兴能源研发费用率下降,“不差钱”募集资金近九成补流?

《港湾商业观察》施子夫 王璐 日前&#xff0c;冲刺创业板的上海汉兴能源科技股份有限公司&#xff08;以下简称&#xff0c;汉兴能源&#xff09;更新了招股书。 2023年6月末&#xff0c;汉兴能源正式递表创业板&#xff0c;保荐机构为长江证券。 从业务属性上来看&#x…

React间的组件通信

一、父传子&#xff08;props&#xff09; 步骤 父组件传递数据&#xff0c;子组件标签身上绑定属性子组件接收数据&#xff0c;props的参数 // 子组件 function Son(props) {return (<div>this is Son, {props.name}</div>) }// 父组件 function App() {const n…