当前位置: 首页 > news >正文

【JavaEE初阶】 CAS详解

文章目录

  • 🌲什么是 CAS
    • 🚩CAS伪代码
  • 🎋CAS 是怎么实现的
  • 🌳CAS的应用
    • 🚩实现原子类
    • 🚩实现自旋锁
  • 🎄CAS 的 ABA 问题
    • 🚩什么是 ABA 问题
    • 🚩ABA 问题引来的 BUG
    • 🚩解决方案
  • 🍀CAS相关面试题
  • ⭕总结

🌲什么是 CAS

CAS: 全称Compare and swap,字面意思:”比较并交换“

这样解释还是很茫然,我们来看一下一个CAS 涉及到的操作就明白CAS是啥了

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

  1. 比较 A 与 V 是否相等。(比较)

  2. 如果比较相等,将 B 写入 V。(交换)

  3. 返回操作是否成功

意思就是我们现再假设有一个寄存器,里面存储的是A和B的值,我们这里为了后面方便叙述,我们另A= 1,B= 2;还有一个内存里面存储的是V的值,此处先假设V = 1

在这里插入图片描述

比较A与V的值,相等就将B与V的值进行交换

在这里插入图片描述

如果操作成功就会返回成功的操作

假设我们这里的V最开始:v = 2;

在这里插入图片描述

此时A与V值不相等,就会返回失败操作

🚩CAS伪代码

需要注意的是下面这段伪代码只是为了理解上面CAS的思想的,并不具有原子性。 真实的 CAS 是一个原子的硬件指令完成的. 这个伪代码只是辅助理解CAS 的工作流程.

boolean CAS(address, expectValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}

CAS就类似于我们之前讲的Synchronized关键字,都是将操作变为原子性,但是呢,CAS只适用于一些特殊场景,并不通用

CAS的好处:

  • 当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号

关于CAS是属于我们讲的那一种锁策略?

我们可以将CAS 视为是一种乐观锁. (或者可以理解成 CAS 是乐观锁的一种实现方式

🎋CAS 是怎么实现的

针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:

  • java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;

  • unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;

  • Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。

简而言之,是因为硬件予以了支持,软件层面才能做到

🌳CAS的应用

🚩实现原子类

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

里面提供了很多方法可以供操作

其中的 getAndIncrement 相当于 i++ 操作.

AtomicInteger atomicInteger = new AtomicInteger(0);
// 相当于 i++
atomicInteger.getAndIncrement();

那这个方法是怎么实现CAS的呢?我们一起来看一下该类的伪代码

class AtomicInteger {private int value;public int getAndIncrement() {int oldValue = value;while ( CAS(value, oldValue, oldValue+1) != true) {oldValue = value;}return oldValue;}
}

假设两个线程同时调用 getAndIncrement

  1. 两个线程都读取 value 的值到 oldValue 中. (oldValue 是一个局部变量, 在栈上每个线程有自己的栈)
    在这里插入图片描述
  2. 线程1 先执行 CAS 操作. 由于 oldValue 和 value 的值相同, 直接进行对 value 赋值

注意:

  • CAS 是直接读写内存的, 而不是操作寄存器.
  • CAS 的读内存, 比较, 写内存操作是一条硬件指令, 是原子的.

在这里插入图片描述

  1. 线程2 再执行 CAS 操作, 第一次 CAS 的时候发现 oldValue 和 value 不相等, 不能进行赋值. 因此需要进入循环
    在循环里重新读取 value 的值赋给 oldValue
    在这里插入图片描述
  2. 线程2 接下来第二次执行 CAS, 此时 oldValue 和 value 相同, 于是直接执行赋值操作

在这里插入图片描述

  1. 线程1 和 线程2 返回各自的 oldValue 的值即可

代码示例如下:

import java.util.concurrent.atomic.AtomicInteger;public class ThreadDemo1 {public static void main(String[] args) throws InterruptedException {AtomicInteger atomicInteger = new AtomicInteger(0);Thread thread1 = new Thread(() -> {for(int i = 0; i < 100; i ++) {atomicInteger.getAndIncrement();}});Thread thread2 = new Thread(() -> {for(int i = 0; i < 100; i ++) {atomicInteger.getAndIncrement();}});thread2.start();thread1.start();thread1.join();thread2.join();System.out.println(atomicInteger);}
}

结果展示:
在这里插入图片描述

通过形如上述代码就可以实现一个原子类. 不需要使用重量级锁, 就可以高效的完成多线程的自增操作.

🚩实现自旋锁

自旋锁是基于 CAS 实现更灵活的锁, 获取到更多的控制权.

自旋锁的伪代码如下:

public class SpinLock {private Thread owner = null;public void lock(){// 通过 CAS 看当前锁是否被某个线程持有.// 如果这个锁已经被别的线程持有, 那么就自旋等待.// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.while(!CAS(this.owner, null, Thread.currentThread())){//一系列操作}}public void unlock (){this.owner = null;}
}

利用上述伪代码的思想就可以实现一个自旋锁了

CAS操作虽然好用,但是也存在一些问题

🎄CAS 的 ABA 问题

🚩什么是 ABA 问题

ABA 的问题:
假设存在两个线程 t1 和 t2. 有一个共享变量 num, 初始值为 A.

接下来, 线程 t1 想使用 CAS 把 num 值改成 Z, 那么就需要

  • 先读取 num 的值, 记录到 oldNum 变量中.

  • 使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z.

但是, 在 t1 执行这两个操作之间, t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A

  • 线程 t1 的 CAS 是期望 num 不变就修改. 但是 num 的值已经被 t2 给改了. 只不过又改成 A 了. 这个时候 t1 究竟是否要更新 num 的值为 Z 呢?

到这一步, t1 线程无法区分当前这个变量始终是 A, 还是经历了一个变化过程

在这里插入图片描述

这就好比, 我们买一个手机, 无法判定这个手机是刚出厂的新手机, 还是别人用旧了, 又翻新过的手机

🚩ABA 问题引来的 BUG

大部分的情况下, t2 线程这样的一个反复横跳改动, 对于 t1 是否修改 num 是没有影响的. 但是不排除一些特殊情况

接下来我为大家举一个例子:

假设 春风老哥 有 100 存款. 春风想从 ATM 取 50 块钱. 取款机创建了两个线程, 并发的来执行 - 50操作.

  • 我们期望一个线程执行 -50 成功,
  • 另一个线程 -50 失败.

如果使用 CAS 的方式来完成这个扣款过程就可能出现问题

正常的过程

  1. 存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更新为 50.

  2. 线程1 执行扣款成功, 存款被改成 50. 线程2 阻塞等待中.

  3. 轮到线程2 执行了, 发现当前存款为 50, 和之前读到的 100 不相同, 执行失败

异常的过程

  1. 存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更新为 50.

  2. 线程1 执行扣款成功, 存款被改成 50. 线程2 阻塞等待中.

  3. 在线程2 执行之前, 春风的朋友正好给滑稽转账 50, 账户余额变成 100 !!

  4. 轮到线程2 执行了, 发现当前存款为 100, 和之前读到的 100 相同, 再次执行扣款操作

这个时候, 扣款操作被执行了两次!!! 都是 ABA 问题搞的鬼!!!

🚩解决方案

给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.

CAS 操作在读取旧值的同时, 也要读取版本号.

真正修改的时候,

  • 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.

  • 如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了)

这就好比, 判定这个手机是否是翻新机, 那么就需要收集每个手机的数据, 第一次挂在电商网站上的手机记为版本1,以后每次这个手机出现在电商网站上, 就把版本号进行递增. 这样如果买家不在意这是翻新机, 就买. 如果买家在意, 就可以直接略过

我们再来利用版本号来解决一下春风老哥的问题

假设 春风老哥 有 100 存款. 春风想从 ATM 取 50 块钱. 取款机创建了两个线程, 并发的来执行 -50操作.
我们期望一个线程执行 -50 成功, 另一个线程 -50 失败.为了解决 ABA 问题, 给余额搭配一个版本号, 初始设为 1.

  1. 存款 100. 线程1 获取到 存款值为 100, 版本号为 1, 期望更新为 50; 线程2 获取到存款值为 100,版本号为 1, 期望更新为 50.
  2. 线程1 执行扣款成功, 存款被改成 50, 版本号改为2. 线程2 阻塞等待中.
  3. 在线程2 执行之前, 春风的朋友正好给春风转账 50, 账户余额变成 100, 版本号变成3.
  4. 轮到线程2 执行了, 发现当前存款为 100, 和之前读到的 100 相同, 但是当前版本号为 3, 之前读到的版本号为 1, 版本小于当前版本, 认为操作失败.

在 Java 标准库中提供了 AtomicStampedReference 类. 这个类可以对某个类进行包装, 在内部就提供了上面描述的版本管理功能.

关于 AtomicStampedReference 的具体用法此处不再展开. 有需要的同学自行查找Java文档了解使用方法即可

🍀CAS相关面试题

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

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

  1. ABA问题怎么解决?

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

⭕总结

关于《【JavaEE初阶】 CAS详解》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

相关文章:

【JavaEE初阶】 CAS详解

文章目录 &#x1f332;什么是 CAS&#x1f6a9;CAS伪代码 &#x1f38b;CAS 是怎么实现的&#x1f333;CAS的应用&#x1f6a9;实现原子类&#x1f6a9;实现自旋锁 &#x1f384;CAS 的 ABA 问题&#x1f6a9;什么是 ABA 问题&#x1f6a9;ABA 问题引来的 BUG&#x1f6a9;解决…...

Docker镜像制作

目录 Dockfile是什么 构建镜像的三个步骤 dockerfile内容基础知识 docker执行一个Dockerfile脚本的大致流程 Dockerfile指令 FROM MAINTAINER RUN EXPOSE WORKDIR ENV ADD COPY VOLUME USER ONBUILD CMD ENTRYPOINT CMD和ENTRYPOINT区别 构建dockerfile Do…...

v-on 可以监听多个方法吗?

目录 ​编辑 前言&#xff1a;Vue 3 中的 v-on 指令 详解&#xff1a;v-on 指令的基本概念 用法&#xff1a;v-on 指令监听多个方法 解析&#xff1a;v-on 指令的优势和局限性 优势 局限性 **v-on 指令的最佳实践** - **适度监听**&#xff1a; - **方法抽离**&#x…...

【Docker】Docker Compose的使用

我们知道使用一个Dockerfile模板文件&#xff0c;可以让用户很方便的定义⼀个单独的应用容器。然而&#xff0c;在日常工作中&#xff0c;经常会碰到需要多个容器相互配合来完成某项任务的情况。 例如要实现一个Web项目&#xff0c;除了Web服务容器本身&#xff0c;往往还需要…...

2023年中国调速器产量、销量及市场规模分析[图]

调速器行业是指生产、销售和维修各种调速器设备的行业。调速器是一种能够改变机械传动系统输出转速的装置&#xff0c;通过调整输入和输出的转速比来实现转速调节的功能。 调速器行业分类 资料来源&#xff1a;共研产业咨询&#xff08;共研网&#xff09; 随着工业自动化程度…...

深入了解JVM调优:解锁Java应用程序性能的秘诀

文章目录 &#x1f34a; JVM调优&#x1f389; 增大Eden 空间大小&#x1f389; 如果MinorGC 频繁&#xff0c;且容易引发 Full GC&#x1f4dd; S1 区大小 < MGC 存活的对象大小&#xff0c;对象的年龄才1岁&#x1f4dd; 相同年龄的对象所占总空间大小>s1区空间大小的一…...

[java进阶]——线程池的使用,自定义线程池

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 一、线程池的存在意义 二、线程池的使用 2.1线程池的核心原理 2.2线程池的代码实现 三、自定义线程池 3.1线程池的参数详解 3.2线程池的执行原理 3.3灵魂两问 3.4线程池多大合适 3.5拒绝策略 一、线程池…...

Linux 进程切换与命令行参数

假设进程1现在要切走了&#xff0c;切入进程2.那进程1就要先保存数据&#xff0c;方便以后恢复&#xff0c; 然后进程2再切走&#xff0c;进程1再把数据还原&#xff1a; 操作系统又分为实时操作系统和分时操作系统。 实时操作系统是是给操作系统一个进程&#xff0c;操作系统…...

Python基础入门例程6-NP6 牛牛的小数输出

目录 描述 输入描述&#xff1a; 输出描述&#xff1a; 示例1 解答&#xff1a; 说明&#xff1a; 描述 牛牛正在学习Python的输出&#xff0c;他想要使用print函数控制小数的位数&#xff0c;你能帮助它把所有读入的数据都保留两位小数输出吗&#xff1f; 输入描述&a…...

传奇游戏常见问题解决办法

GEE合区出现错误常规解决方案 GEE合区出现错误大部分因数据库损坏导致的合区报错&#xff0c;如果合区提示内存不足&#xff0c;更新64位合区&#xff0c;使用64位合区工具在服务器上进行合并&#xff0c;合区需要将2个区数据大部分提取到内存中&#xff0c;32位合区工具支持内…...

2310D的dll问题

原文 我正在开发一个游戏引擎,偶然发现了一些空针问题. 考虑此简单程序: class Test {void doIt(){} } void main() {Test t;t.doIt(); }它编译,然后在Linux上使用DMD时,用11信号干掉了. 如果使用Java,甚至不会构建该程序,因为它会失败,说明从未初化它. 但我不关心分析器,我宁…...

包管理工具

代码共享方案 放到npm仓库&#xff0c;下载到本地放到node_modules npm配置文件 必须填写的属性&#xff1a;name、version name是项目的名称&#xff1b; version是当前项目的版本号&#xff1b; description是描述信息&#xff0c;很多时候是作为项目的基本描述&#xff1b…...

Qt第六十五章:自定义菜单栏的隐藏、弹出

目录 一、效果图 二、qtDesigner 三、ui文件如下&#xff1a; 四、代码 一、效果图 二、qtDesigner 原理是利用属性动画来控制QFrame的minimumWidth属性。 ①先拖出相应的控件 ②布局一下 ③填上一些样式 相关QSS background-color: rgb(238, 242, 255); border:2px sol…...

element table中嵌套el-select 无法选择问题

<el-table-column align"left" label"姓名" show-overflow-tooltip :key"tableKey"><template slot-scope"scope"><el-select placeholder"请选择" :disabled"!saveButton" v-model"scope.ro…...

2.6.C++项目:网络版五子棋对战之数据管理模块-游戏房间管理模块的设计

文章目录 一、意义二、功能三、作用四、游戏房间类基本框架五、游戏房间管理类基本框架七、游戏房间类代码八、游戏房间管理类代码 一、意义 对匹配成功的玩家创建房间&#xff0c;建立起一个小范围的玩家之间的关联关系&#xff01; 房间里一个玩家产生的动作将会广播给房间里…...

计算机视觉中的数据预处理与模型训练技巧总结

计算机视觉主要问题有图像分类、目标检测和图像分割等。针对图像分类任务&#xff0c;提升准确率的方法路线有两条&#xff0c;一个是模型的修改&#xff0c;另一个是各种数据处理和训练的技巧(tricks)。图像分类中的各种技巧对于目标检测、图像分割等任务也有很好的作用&#…...

GeoHash分享

写在前边 复制的一个内部分享&#xff0c;所以可能更偏向PPT性质&#xff0c;本文提出的问题&#xff0c;在末尾参考材料中都会有所提及&#xff0c;包括更深层次的实现原理和各大API对于GeoHash的优化。感兴趣的读者可以拓展看一下。 START GeoHash是一种地址编码&#xff…...

【超详细】CentOS 7安装MySQL 5.7【安装及密码配置、字符集配置、远程连接配置】

准备工作&#xff1a;CentOS 7系统&#xff0c;并确保可以联通网络 1、获取MySQL 5.7 Community Repository软件包 注意&#xff1a;这里使用的是root用户身份。 wget https://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm2、安装软件包 rpm -ivh mysql5…...

Elasticsearch 8.X 分词插件版本更新不及时解决方案

1、关于 Elasticsearch 8.X IK 分词插件相关问题 球友在 ElasticSearch 版本选型问题中提及&#xff1a;如果要使用ik插件&#xff0c;是不是就使用目前最新的IK对应elasticsearch的版本“8.8.2”&#xff1f; https://github.com/medcl/elasticsearch-analysis-ik/releases/ta…...

Delete `␍`eslintprettier/prettier

将CRLF改为LF 然后就消失了 除此之外,也可以修改git全局配置 git config --global core.autocrlf false...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

Linux系统部署KES

1、安装准备 1.版本说明V008R006C009B0014 V008&#xff1a;是version产品的大版本。 R006&#xff1a;是release产品特性版本。 C009&#xff1a;是通用版 B0014&#xff1a;是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存&#xff1a;1GB 以上 硬盘&#xf…...

「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案

在移动互联网营销竞争白热化的当下&#xff0c;推客小程序系统凭借其裂变传播、精准营销等特性&#xff0c;成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径&#xff0c;助力开发者打造具有市场竞争力的营销工具。​ 一、系统核心功能架构&…...