Java 之 CAS(CompareAndSwap)底层原理详解

目录

一. 前言

二. CAS 底层原理

2.1. 代码实例

2.2. 源码分析

2.3. 底层汇编

2.4. ABA 问题

三. 总结

3.1. CAS 缺点

3.2. CAS 会导致 ABA 问题


 

一. 前言

    CAS 的全称是 Compare-And-Swap,它是 CPU 并发原语。它的功能是判断内存某个位置的值是否为预期值。如果是则更改为新的值,这个过程是原子的。 CAS 并发原语体现在 Java 语言中就是 sun.misc.Unsafe 类的各个方法。调用 UnSafe 类中的 CAS 方法,JVM 会帮我们实现出 CAS汇编指令,这是一种完全依赖于硬件的功能,通过它实现了原子操作,再次强调,由于 CAS 是一种系统原语,原语属于操作系统作用范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说 CAS 是一条 CPU 的原子指令,不会造成所谓的数据不一致的问题,也就是说 CAS 是线程安全的。

二. CAS 底层原理

2.1. 代码实例

首先使用 AtomicInteger 创建了一个实例,并初始化为 5:

// 创建一个原子类
AtomicInteger atomicInteger = new AtomicInteger(5);

然后调用 CAS 方法,企图更新成 2024,这里有两个参数,一个是 5,表示期望值,第二个就是我们要更新的值:

atomicInteger.compareAndSet(5, 2024);

然后再次使用了一个方法,同样将值改为 1024:

atomicInteger.compareAndSet(5, 1024);

完整代码如下:

public class CASDemo {public static void main(String[] args) {// 创建一个原子类AtomicInteger atomicInteger = new AtomicInteger(5);/*** 一个是期望值,一个是更新值,但期望值和原来的值相同时,才能够更改* 假设三秒前,我拿的是5,也就是expect为5,然后我需要更新成 2024*/System.out.println(atomicInteger.compareAndSet(5, 2024) + "\t current data: " + atomicInteger.get());System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t current data: " + atomicInteger.get());}
}

上面代码的执行结果为:

true     current date: 2024
false    current date: 2024

这是因为我们执行第一个的时候,期望值和原本值是满足的,因此修改成功,但是第二次后,主内存的值已经改成了 2024,不满足期望值,因此返回了 false,本次写入失败。

03ca51ab2171461199e5e50e34cae9b5.png

这个就类似于 SVN 或者 Git 的版本号,如果没有人更改过,就能够正常提交,否则需要先将代码pull 下来,合并代码后,然后提交。

2.2. 源码分析

首先我们先看看 atomicInteger.getAndIncrement() 方法的源码:

bd02318431f445ae95b583f6eca79343.png

从这里能够看到,底层又调用了一个 unsafe 类的 getAndAddInt() 方法。

unsafe 类:

c506671021374a5b8299ac5a0f11d1f6.jpeg

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

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

为什么 Atomic 修饰的包装类,能够保证原子性,依靠的就是底层的 Unsafe 类。关于 Atomic 相关的包装类详细介绍可以参见《JUC之Atomic原子类》。

变量 valueOffset:

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

ce64899eb22c4fa39a555beb37f50ec9.png

从这里我们可以看到,通过 valueOffset,直接通过内存地址,获取到值,然后进行加 1 操作。

变量 value 用 volatile 修饰:

保证了多线程之间的内存可见性。

924525f8bfae444c99ecf90874fe9132.png

var5:就是我们从主内存中拷贝到工作内存中的值。

那么操作的时候,需要比较工作内存中的值,和主内存中的值进行比较。

假设执行 compareAndSwapInt 返回 false,那么就一直执行 while 方法,直到期望的值和真实值一样

  • val1:AtomicInteger 对象本身。
  • var2:该对象值得引用地址。
  • var4:需要变动的数量。
  • var5:用 var1 和 var2 找到的内存中的真实值
    • 用该对象当前的值与 var5 比较;
    • 如果相同,更新 var5 + var4 并返回 true;
    • 如果不同,继续取值然后再比较,直到更新完成。

这里没有用 synchronized,而用 CAS,这样提高了并发性,也能够实现一致性,是因为每个线程进来后,进入的 do while 循环,然后不断的获取内存中的值,判断是否为最新,然后在进行更新操作。

假设线程A和线程B同时执行getAndInt操作(分别跑在不同的CPU上):

  1. AtomicInteger 里面的 value 原始值为3,即主内存中 AtomicInteger 的 value 为3,根据 JMM模型,线程 A 和线程 B 各自持有一份价值为3的副本,分别存储在各自的工作内存;
  2. 线程 A 通过 getIntVolatile(var1, var2)  拿到 value 值3,这时线程 A 被挂起(该线程失去 CPU 执行权);
  3. 线程 B 也通过 getIntVolatile(var1, var2) 方法获取到 value 值也是3,此时刚好线程 B 没有被挂起,并执行了 compareAndSwapInt 方法,比较内存的值也是3,成功修改内存值为4,线程 B 打完收工,一切OK;
  4. 这时线程 A 恢复,执行 CAS 方法,比较发现自己手里的数字3和主内存中的数字4不一致,说明该值已经被其它线程抢先一步修改过了,那么 A 线程本次修改失败,只能够重新读取后再来一遍了,也就是在执行 do while;
  5. 线程 A 重新获取 value 值,因为变量 value 被 volatile 修饰,所以其它线程对它的修改,线程 A 总能够看到,线程 A 继续执行 compareAndSwapInt 进行比较替换,直到成功。

Unsafe 类 + CAS 思想: 也就是自旋,自我旋转。

2.3. 底层汇编

Unsafe 类中的 compareAndSwapInt 是一个本地方法,该方法的实现位于 unsafe.cpp 中:

  1. 先想办法拿到变量 value 在内存中的地址;
  2. 通过 Atomic::cmpxchg 实现比较替换,其中参数 X 是即将更新的值,参数 e 是原内存的值。

 4662407db0b34ef3ab2e0b4ec5fe1a6c.jpeg

2.4. ABA 问题

    假设两个线程 T1 和 T2 访问同一个变量 V,当 T1 访问变量 V 时,读取到 V 的值为 A;此时线程 T1 被抢占了,T2 开始执行,T2 先将变量 V 的值从 A 变成 B,然后又将变量 V 的值从 B 变成A;此时 T1 又抢占了主动权,继续执行,它发现 V 的值还是 A,以为没有变化,所以就继续执行了。这个过程中,变量 V 从 A 变为 B,再由 B 变为 A 就形象地称为 ABA 问题。

三. 总结

3.1. CAS 缺点

CAS 不加锁,保证一次性,但是需要多次比较。

1. 循环时间长,开销大(因为执行的是 do while,如果比较不成功一直在循环,最差的情况,就是某个线程一直取到的值和预期值都不一样,这样就会无限循环)。

2. 只能保证一个共享变量的原子操作:

  • 当对一个共享变量执行操作时,我们可以通过循环 CAS 的方式来保证原子操作;
  • 但是对于多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候只能用锁来保证原子性。

3. 引出 ABA 问题

3.2. CAS 会导致 ABA 问题

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

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

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

 

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

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

相关文章

MySQL 8.0 开关 Redo Logging

一 前言 前几天有客户测试使用云数据库的时候提出 要禁止mydumper 关闭redo log的操作 (说白了就是导入数据时保持MySQL 实例的redo logging功能), 这才想起 在 MySQL 8.0.21 版本中,开启了一个新特性 “Redo Logging 动态开关”。 在新实例导数据的场…

【IPC通信--消息队列】

消息队列(也叫做报文队列)是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息…

[C#]Onnxruntime部署Chinese CLIP实现以文搜图以文找图功能

【官方框架地址】 https://github.com/OFA-Sys/Chinese-CLIP 【算法介绍】 在当今的大数据时代,文本信息处理已经成为了计算机科学领域的核心议题之一。为了高效地处理海量的文本数据,自然语言处理(NLP)技术应运而生。而在诸多N…

电子实验室设备:从零开始配置实验室(一)

本文译自 Electronics Lab Equipment: Kitting out a Lab from Scratch 随着多次国际迁徙以及在几家公司(或其分支机构)工作,尤其是在没有强大电子工程团队的情况下,我不得不为自己和客户设置多个电子实验室。那些计划进行内部测试…

网页设计与制作web前端设计html+css+js成品。电脑网站制作代开发。vscodeDrea 【企业公司宣传网站(HTML静态网页项目实战)附源码】

网页设计与制作web前端设计htmlcssjs成品。电脑网站制作代开发。vscodeDrea 【企业公司宣传网站(HTML静态网页项目实战)附源码】 https://www.bilibili.com/video/BV1Hp4y1o7RY/?share_sourcecopy_web&vd_sourced43766e8ddfffd1f1a1165a3e72d7605

【C++】STL 算法 ⑥ ( 二元谓词 | std::sort 算法简介 | 为 std::sort 算法设置 二元谓词 排序规则 )

文章目录 一、二元谓词1、二元谓词简介2、 std::sort 算法简介3、 代码示例 - 为 std::sort 算法设置 二元谓词 排序规则 一、二元谓词 1、二元谓词简介 " 谓词 ( Predicate ) " 是一个 返回 布尔 bool 类型值 的 函数对象 / 仿函数 或 Lambda 表达式 / 普通函数 , …

逆置算法和数组循环移动算法

元素逆置 概述:其实就是将 第一个元素和最后一个元素交换,第二个元素和倒数第二个元素交换,依次到中间位置。用途:可用于数组的移动,字符串反转,链表反转操作,栈和队列反转等操作。 逆置图解 …

2024年1月7日15:09:50

2024年1月7日15:09:55复习:我今天学了有价值的东西,那就是在瓦罗兰特拿到了三杀 2024年1月7日15:11:10学习了如何使用vivopad2的键盘 可以稍微用一下 2024年1月7日15:17:58 学习一个编程的题目 2024年1月7日15:31:27不用机械键盘打字效率就是比不用低…

深入理解堆(Heap):一个强大的数据结构

. 个人主页:晓风飞 专栏:数据结构|Linux|C语言 路漫漫其修远兮,吾将上下而求索 文章目录 前言堆的实现基本操作结构体定义初始化堆(HeapInit)销毁堆(HeapDestroy) 重要函数交换函数(…

Talk | EMNLP 2023 最佳长论文:以标签为锚-从信息流动的视角分析上下文学习

本期为TechBeat人工智能社区第561期线上Talk。 北京时间1月4日(周四)20:00,北京大学博士生—王乐安的Talk已准时在TechBeat人工智能社区开播! 他与大家分享的主题是: “以标签为锚-从信息流动的视角分析上下文学习”,介绍了他的团队在上下文学…

STM32深入系列02——BootLoader分析与实现

文章目录 1. STM32程序升级方法1.1 ST-Link / J-link下载1.2 ISP(In System Programing)1.3 IAP(In Applicating Programing)1.3.1 正常程序运行流程1.3.2 有IAP时程序运行流程 2. STM32 Bootloader实现2.1 方式一:Boo…

Qt/QML编程学习之心得:Linux下Thread线程创建(26)

GUI设计中经常为了不将界面卡死,会用到线程Thread,而作为GUI设计工具,Qt也提供了一个这样的类,即QThread。 QThread对象管理程序中的一个控制线程。线程QThread开始在run()中执行。默认情况下,run()通过调用exec()启动事件循环,并在线程内运行Qt事件循环。 也可以通过…

【第6期】使用Iview的Select组件进行远程搜索并在编辑时设置一个或多个默认值

本期简介 下拉框这个组件用的地方非常多,普通用法就是将数据列表一次性查询渲染,在列表里面直接本地搜索,优点是可缓存、速度快,但在某些场合并不适用,比如要在下拉框中选择一所中国的学校,幼儿园/小学/初…

20240107查看Android11下移远的4G模块EC20在Firefly的AIO-3399J开发板跑通时的相关服务

20240107查看Android11下移远的4G模块EC20在Firefly的AIO-3399J开发板跑通时的相关服务 2024/1/7 11:24 缘起:友善之臂的SDK:rk3399-android-11-r20211216.tar.xz可以跑通EC20,但是Toybrick的不行! 同样是Andrid11,因此…

Python的核心知识点整理大全66(已完结撒花)

目录 D.3 忽略文件 .gitignore 注意 D.4 初始化仓库 D.5 检查状态 D.6 将文件加入到仓库中 D.7 执行提交 D.8 查看提交历史 D.9 第二次提交 hello_world.py D.10 撤销修改 hello_world.py 注意 D.11 检出以前的提交 往期快速传送门👆(在文…

外贸独立站建站详细操作流程一览,跨境电商卖家营销必看!

独立站是一个独立的网站,包括有独立的服务器,独立的网站程序以及网站域名。关于独立站的优势已经说了很多,本文就不再细谈,想了解的小伙伴可以自行查找之前发布的文章观看。 今天就来说说搭建独立站的详细步骤都有哪些&#xff1f…

Docker mysql 主从复制

目录 介绍:为什么需要进行mysql的主从复制 主从复制原理: ✨主从环境搭建 主从一般面试问题: 介绍:为什么需要进行mysql的主从复制 在实际的生产中,为了解决Mysql的单点故障已经提高MySQL的整体服务性能&#xff…

【AI视野·今日NLP 自然语言处理论文速览 第七十期】Thu, 4 Jan 2024

AI视野今日CS.NLP 自然语言处理论文速览 Thu, 4 Jan 2024 Totally 29 papers 👉上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Multilingual Instruction Tuning With Just a Pinch of Multilinguality Authors Uri Shaham, Jonathan Herzi…

3D点云平面拟合算法

假设你有一组 3D 中的 n 个点,并且想要为它们拟合一个平面。 在本文中,我将推导出一个简单的、数值稳定的方法,并提供它的源代码。 听起来很好玩? 我们开始吧! NSDT工具推荐: Three.js AI纹理开发包 - YOLO…

阿里云服务器“可用区”是什么意思?

阿里云可用区是什么意思?可用区是同一个地域下电力和网络相互独立的区域,可用区是用来搭建高可用高容灾应用架构的,因为可用区之间可以做到故障隔离。阿里云服务器网aliyunfuwuqi.com简单说下可用区什么是可用区及可用区的选择方法&#xff1…