【JavaEE】CAS(Compare And Swap)操作

在这里插入图片描述

文章目录

  • 什么是 CAS
  • CAS 的应用
  • 如何使用 CAS 操作实现自旋锁
  • CAS 的 ABA 问题
  • CAS 相关面试题

什么是 CAS

CAS(Compare and Swap)是一种原子操作,用于在无锁情况下保证数据一致性的问题。它包含三个操作数——内存位置、预期原值及更新值。在执行CAS操作时,会将内存位置的值与预期原值进行比较。如果两者相等,则处理器会自动将该位置的值更新为新值;如果不相等,则处理器不做任何操作。这个过程是原子的,即在整个操作期间,不会被其他线程或进程中断。

在多线程并发编程中,CAS操作可以避免传统的锁机制引起的线程阻塞和上下文切换等问题,提高程序的并发性能。

CAS伪代码

boolean CAS(address, expectValue, swapValue) {//如果内存address中的值和expectValue相等话,//就将swapValue的值赋给adress,并且返回trueif (&address == expectValue) {&address = swapValue;return true;}return false;
}

CAS 是一个 CPU 指令,具有原子性,而具有原子性的操作就代表着不需要加锁就可以保证线程的安全,所以 CAS 操作就可以替代某些加锁的操作。

CAS 本质上是 CPU 提供的指令,然后被操作系统封装形成 API 后,又被 JVM 或者其它封装成为 API 之后,我们程序员才可以直接使用 CAS 的相关操作。

CAS 的应用

CAS 经过 CPU 和 JVM 封装之后,我们在 Java 代码中就可以直接使用 CAS 操作,那么我们来看看在 Java 中如何使用 CAS 操作。

标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的.

在这里插入图片描述

我们可以根据需要创建出合适的类,如果你要进行 CAS 的数据类型为 int 类型的话,就创建 AtomicInteger 类,如果是 boolean 类型的话,就创建出 AtomicBoolean 类型。

在这里插入图片描述

AtomicInteger 类中有很多方法,但是我们今天主要了解 getAndDecrement 方法和 getAndIncrement 方法,它们分别表示–和++操作。

public class Test {public static void main(String[] args) throws InterruptedException {AtomicInteger atomicInteger = new AtomicInteger(0);Thread t1 = new Thread(() -> {for(int i = 0; i < 1000; i++) {atomicInteger.getAndIncrement();}});Thread t2 = new Thread(() -> {for(int i = 0; i < 1000; i++) {atomicInteger.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println(atomicInteger.get());  //2000}
}
public class Test {public static void main(String[] args) throws InterruptedException {AtomicInteger atomicInteger = new AtomicInteger(10000);Thread t1 = new Thread(() -> {for(int i = 0; i < 1000; i++) {atomicInteger.getAndDecrement();}});Thread t2 = new Thread(() -> {for(int i = 0; i < 1000; i++) {atomicInteger.getAndDecrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println(atomicInteger.get());  //8000}
}

++或者–的操作不具有原子性,如果在多线程中进行++或者–操作的时候往往会发生线程不安全问题,导致最终的结果不是我们想要的结果,而这里我们使用 CAS 操作的话就保证了++和–操作的原子性,并且也避免了加锁阻塞的现象,既保证了答案的正确性,又保证了运行速度。

在这里插入图片描述
查看 getAndIncrement 方法我们可以看到这个方法里面又调用了 getAndAddInt 方法,但是这个方法是属于 unsafe 的,unsafe 中的方法都是偏底层且操作较危险的操作。

在这里插入图片描述

可以看到 getAndAddInt 方法中是没有加锁操作的。

在这里插入图片描述
compareAndSwapInt 方法是 native 修饰的本地方法,这个方法是 JVM 底层由 C/C++ 写的,我们是看不到的。

这里 getAndIncremnet 方法还是用伪代码来实现一遍。

class AtomicInteger {private int value;public int getAndIncrement() {//这里现在寄存器当中存储value的值int oldValue = value;//比较内存中的value值是否和寄存器当中的oldValue相同//如果相同,则说明该过程中value的值没有被修改,然后将后面的修改值赋给value//如果不相同,说明在这个过程中value的值被修改了,那么更新oldValue的值while (CAS(value, oldValue, oldValue + 1) != true)oldValue = value;}return oldValue;}
}

如何使用 CAS 操作实现自旋锁

前面【JavaEE】锁策略中为大家讲解了什么是自旋锁,自旋锁就是当线程想要获取到锁,但是这个锁正别其他线程使用的时候,一般请情况下线程会进入阻塞等待状态,但是自旋锁不是,它不释放 CPU 资源,反复确认这个锁是否被释放,使得整个操作一直处于用户态操作,减少了内核态操作而增加一些其他操作。接下来,我们就来使用 CAS 操作来实现自旋锁。

public class SpinLock {//owner表示当前锁是被哪个线程所拥有,当owner为null的时候表示该所可以被获取private Thread owner = null;//while判断当前owner时候为null,如果是,则获取到整个锁,修改owner为当前线程//如果owner不为null,则表示锁被其他线程使用,那么就会返回false,while里面的//判断就为true,进入死循环,直到其他线程使用unlock方法释放锁public void lock() {while (!CAS(this.owner, null, Thread.currentThread())) {}}public void unlock() {this.owner = null;}
}

通过 CAS 操作就解决了当多个线程竞争一个锁的时候,线程进入阻塞等待状态由用户态操作转为内核态的情况,保证了程序处于用户态的操作状态。

CAS 的 ABA 问题

CAS 操作是判断内存中的数据是否和寄存器中的值相等,那么是否会发生一种情况就是:在这个过程中内存中的数据由 A -> B -> A,也就是说内存中的数据被修改了一次,但是最后又被改回来了的情况呢?当然是可能的,那么如果发生这种情况的时候是否会出现问题呢?

使用 CAS 操作的时候,如果发生 ABA 的问题时,一般不会出现问题,但是有些特殊的情况会造成问题。比如:我现在是大学生,每个月我的父母就会向我的银行卡里面打钱,我呢手机绑定了银行卡,就需要从银行卡中将这些钱充值到微信或者支付宝上,我打算充值1000块,但是当我点击充值按钮的时候,因为网卡,我点了一次没反应,所以我又点了一次,当网络好了的时候,它后台就显示我点击了两次,但是实际上我只想充值一次,那么微信或者支付宝的后台就会有两个线程执行 CAS 操作。

在这里插入图片描述

但是如果在这个时候,我的父母又给我银行卡里面打了1000块钱的时候会发生什么呢?

在这里插入图片描述
在这里插入图片描述
那么如何解决 CAS 的 ABA 问题呢?造成 ABA 的问题就是变量既有增加也有减少,如果我们使用的变量是只增或者只减的话,那么就不会发生这种 ABA 问题。我们可以引入一个额外的变量:版本号,换个版本号是只增的,修改一次余额就增加版本号一次,当执行 CAS 操作的时候会判断内存中的版本号和寄存器当中的版本号是否相同,相同则可以执行,不相同就说明中间穿插了其他的修改操作,不执行修改操作。

public class Test2 {private int value;//number表示版本号private int number = 0;public void add(int money) {int oldNumber = number;//进行一次修改操作之后版本号就加1if (CAS(number, oldNumber, oldNumber + 1)) {value += money;}}
}

CAS 相关面试题

1) 讲解下你自己理解的 CAS 机制

全称 Compare and swap, 即 “比较并交换”. 相当于通过一个原子的操作, 同时完成 “读取内存, 比较是否相等, 修改内存” 这三个步骤. 本质上需要 CPU 指令的支撑

2) ABA问题怎么解决?

给要修改的数据引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.如果发现当前版本号和之前读到的版本号一致, 就真正执行修改操作, 并让版本号自增; 如果发现当前版本号比之前读到的版本号大, 就认为操作失败

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

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

相关文章

轻量自高斯注意力(LSGA)机制

light&#xff08;轻量&#xff09;Self-Gaussian-Attention vision transformer&#xff08;高斯自注意力视觉transformer&#xff09; for hyperspectral image classification&#xff08;高光谱图像分类&#xff09; 论文&#xff1a;Light Self-Gaussian-Attention Vision…

完整指南:如何使用 Node.js 复制文件

文件拷贝指的是将一个文件的数据复制到另一个文件中&#xff0c;使目标文件与源文件内容一致。Node.js 提供了文件系统模块 fs&#xff0c;通过该模块可以访问文件系统&#xff0c;实现文件操作&#xff0c;包括拷贝文件。 Node.js 中文件拷贝方法 在 Node.js 中&#xff0c;有…

基于微信小程序的宠物寄养平台小程序设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

预编译(1)

目录 预定义符号&#xff1a; 使用&#xff1a; 结果&#xff1a; 预编译前后对比&#xff1a; #define定义常量&#xff1a; 基本语法&#xff1a; 举例1&#xff1a; 结果&#xff1a; 预编译前后对比&#xff1a; 举例2&#xff1a; 预编译前后对比&#xff1a; 注…

ELK介绍

一、前言 前面的章节我们介绍通过ES Client将数据同步到ElasticSearch中&#xff0c;但是像日志这种数据没有必要自己写代码同步到ES那样会折腾死&#xff0c;直接采用ELK方案就好&#xff0c;ELK是Elasticsearch、Logstash、Kibana三款开源软件的缩写&#xff0c;ELK主要用于…

【论文阅读】大语言模型中的文化道德规范知识

摘要&#xff1a; 在已有的研究中&#xff0c;我们知道英语语言模型中包含了类人的道德偏见&#xff0c;但从未有研究去检测语言模型对不同国家文化的道德差异。 我们分析了语言模型包含不同国家文化道德规范的程度&#xff0c;主要针对两个方面&#xff0c;其一是看语言模型…

beanstalkd 启动跟停止【经常使用 nohup 和 配合来启动程序,如: nohup ./test 同时免疫SIGINT和SIGHUP信号】

启动命令&#xff1a;  nohup /usr/bin/beanstalkd -l 0.0.0.0 -p 11300 & >> /dev/null 2>&1 正常启动后&#xff0c;利用 【lsof -i:11300】查看 该服务是否正常启动 停止命令&#xff1a; /etc/init.d/beanstalkd stop 正常停止后&#xff0c;利用 【l…

P2PNet-Soy原理梳理

前文总结了P2PNet源码以及P2PNet-Soy源码实现方法&#xff0c;相关链接如下&#xff1a; 人群计数P2PNet论文&#xff1a;[2107.12746] Rethinking Counting and Localization in Crowds:A Purely Point-Based Framework (arxiv.org) p2p人群计数源码&#xff1a;GitHub - Te…

Java Spring Boot 开发框架

Spring Boot是一种基于Java编程语言的开发框架&#xff0c;它的目标是简化Java应用程序的开发过程。Spring Boot提供了一种快速、易于使用的方式来创建独立的、生产级别的Java应用程序。本文将介绍Spring Boot的特性、优势以及如何使用它来开发高效、可靠的应用程序。 一、简介…

云服务器租用价格表概览_阿里云腾讯云华为云

云服务器租用价格多少钱一年&#xff1f;阿腾云分享阿里云、腾讯云和华为云的云服务器租用价格表&#xff1a;阿里云2核2G服务器108元一年起、腾讯云2核2G3M带宽轻量服务器95元一年、华为云2核2G3M云耀L实例89元一年起&#xff0c;阿腾云分享更多关于云服务器租用价格明细&…

Kubernetes基础(五)-Service

1 引言 Service 主要用于提供网络服务&#xff0c;通过Servicel的定义&#xff0c;能够 为客户端应用提供稳定的访问地址&#xff08;域名或IP地址&#xff09;和负载均衡功能&#xff0c;以及屏蔽后端Endpoint的变化&#xff0c;是Kubernetes实现微服务的核心资源。 本文详细…

博弈论中静态博弈经典场景案例

博弈论中静态博弈经典场景案例 1、齐威王田忌赛马 田忌赛马是中国家喻户晓的故事&#xff0c;故事讲述的是齐国大将田忌的谋士孙膑如何运用计谋帮助田忌在与齐威王赛马时以弱胜强的故事&#xff0c;这个故事其实本质也是一个博弈的过程。     齐威王要和田忌赛马&#xff…

二叉树MFC实现

设有一颗二叉树如下&#xff1b; 这似乎是一颗经常用作示例的二叉树&#xff1b; 对树进行遍历的结果是&#xff0c; 先序为&#xff1a;3、2、2、3、8、6、5、4&#xff0c; 中序为&#xff1a;2、2、3、3、4、5、6、8&#xff0c; 后序为2、3、2、4、5、6、8、3&#xff1b…

MySQL学习笔记25

逻辑备份 物理备份 在线热备&#xff1a; 真实案例&#xff1a; 数据库架构是一主两从&#xff0c;但是两台从数据库和主数据不同步。但是每天会全库备份主服务器上的数据到从服务器上。需要解决主从不同步的问题。 案例背后的核心技术&#xff1a; 1、熟悉MySQL数据库常见…

【Java】面向过程和面向对象思想||对象和类

1.面向过程和面向对象思想 两者都贯穿于软件分析、设计和开发的各个阶段&#xff0c;对应面向对象就分别称为面向对象的分析&#xff08;OOA&#xff09;、面向对象的设计&#xff08;OOD&#xff09;和面向对象的编程&#xff08;OOP&#xff09;。C语言是一种典型的面向过程语…

【计算机视觉|人脸建模】PanoHead:360度几何感知的3D全头合成

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;PanoHead: Geometry-Aware 3D Full-Head Synthesis in 360 ∘ ^{\circ} ∘ 链接&#xff1a;[2303.13071] PanoHead: Geometry-Aware 3D Full-Head Synthesis in 360 ∘ ^{\circ} ∘ (arx…

python reportlab生成pdf

这里自定义了pagetemplate&#xff0c;使用BaseDocTemplate&#xff0c;但我感觉一般使用SimpleDocTemplate就可以。 from reportlab.platypus import Frame from reportlab.lib.pagesizes import A4, landscapepadding dict(leftPadding72,rightPadding72,topPadding72,bott…

Java面经整理(2)

一)为什么要使用克隆? 实现原型设计模式&#xff0c;实现备份和恢复 假设此时这个系统是支持用户进行配置的&#xff0c;是支持用户设置皮肤的颜色&#xff0c;设置系统的快捷键&#xff0c;此时就需要使用原型设计模式&#xff0c;不能自己设置把别人的设置都给改了&#xff…

大数据Doris(三):Doris编译部署篇

文章目录 Doris编译部署篇 一、Doris编译