CAS详解

news2025/7/27 12:15:17

CAS详解

  • 一 简介
  • 二 CAS底层原理
    • 2.1.AtomicInteger内部的重要参数
    • 2.2.AtomicInteger.getAndIncrement()分析
      • 2.2.1.getAndIncrement()方法分析
      • 2.2.2.举例分析
  • 三 CAS缺点
  • 四 CAS会导致"ABA问题"
    • 4.1.AtomicReference 原⼦引⽤。
    • 4.2.ABA问题的解决(AtomicStampedReference 类似于时间戳)
      • 4.2.1 ABA问题的产生演示
      • 4.2.2.ABA问题的解决

一 简介

CAS的全称为Compare-And-Swap,⽐较并交换,是⼀种很重要的同步思想。它是⼀条CPU并发原语。
它的功能是判断主内存某个位置的值是否为跟期望值⼀样,相同就进⾏修改,否则⼀直重试,直到⼀致为⽌。这个过程是原⼦的。

看下⾯这段代码,思考运⾏结果是

import java.util.concurrent.atomic.AtomicInteger;

/**
 * CAS的全称为Compare-And-Swap,比较并交换,是一种很重要的同步思想。它是一条CPU并发原语。
 * 它的功能是判断主内存某个位置的值是否为跟期望值一样,相同就进行修改,否则一直重试,直到一致为止。这个过程是原子的。
 */
public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        //CAS操作
        System.out.println(atomicInteger.compareAndSet(5, 2000) + "\t最终值:" + atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t最终值:" + atomicInteger.get());
        //true	最终值:2000
        //false	最终值:2000
    }
}

分析

第⼀次修改,期望值为5,主内存也为5,修改成功,为2000。
第⼆次修改,期望值为5,主内存为2000,修改失败,需要重新获取主内存的值 。

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

二 CAS底层原理

在这里插入图片描述

2.1.AtomicInteger内部的重要参数

  1. Unsafe
    是CAS的核⼼类,由于Java⽅法⽆法直接访问底层系统,需要通过本地(native)⽅法来访问,Unsafe 相当于⼀个后⾯,基于该类可以直接操作特定内存的数据。Unsafe类存在于sum.misc包中,其内部⽅ 法操作可以像C的指针⼀样直接操作内存,因为Java中CAS操作的执⾏依赖于Unsafe类的⽅法。
    注意Unsafe类中的所有⽅法都是native修饰的,也就是说Unsafe类中的⽅法都直接调⽤操作系统底层 资源执⾏相应任务
  2. 变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数 据的。
  3. 变量value⽤volatile修饰,保证了多线程之间的内存可⻅性。

2.2.AtomicInteger.getAndIncrement()分析

2.2.1.getAndIncrement()方法分析

AtomicInteger.getAndIncrement()调⽤了 Unsafe.getAndAddInt()⽅法。 Unsafe类的⼤部分 ⽅法都是 native的,⽤来像C语⾔⼀样从底层操作内存。
在这里插入图片描述
C语句代码JNI,对应java⽅法 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5)

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset,jint e, jint x)) 
UnsafeWrapper("Unsafe_CompareAndSwapInt"); 
oop p = JNIHandles::resolve(obj); 
jint* add = (jint *)index_oop_from_field_offset_long(p, offset); return (jint)(Atomic::cmpxchg(x,addr,e))==e; 
UNSAFE_END 
//先想办法拿到变量value在内存中的地址addr。 
//通过Atomic::cmpxchg实现⽐较替换,其中参数x是即将更新的值,参数e是原内存的值

在这里插入图片描述

这个⽅法的var1和var2,就是根据对象和偏移量得到在主内存的快照值var5。然 后
compareAndSwapInt⽅法通过var1和var2得到当前主内存的实际值。如果这个实际值跟快照值相
等,那么就更新主内存的值为var5+var4。如果不等,那么就⼀直循环,⼀直获取快照,⼀直对⽐,直 到实际值和快照值相等为⽌。

参数介绍
var1 AtomicInteger对象本身
var2 该对象值的引⽤地址
var4 需要变动的数量
var5 是通过var1和var2,根据对象和偏移量得到在主内存的快照值var5

2.2.2.举例分析

⽐如有A、B两个线程,⼀开始都从主内存中拷⻉了原值为3,
A线程执⾏到 var5=this.getIntVolatile,即var5=3。
此时A线程挂起,B修改原值为4,B线程执⾏完毕,由于加了volatile,所以这个修改是⽴即可⻅的。
A线程被唤醒,执⾏ this.compareAndSwapInt()⽅法,发现这个时候主内存的值不等于快照值3,所以继续循环,重新从主内存获取。
在这里插入图片描述

三 CAS缺点

在这里插入图片描述

CAS实际上是⼀种⾃旋锁,

  1. ⼀直循环,开销⽐较⼤。我们可以看到getAndAddInt⽅法执⾏时,有个do while,如果CAS失 败,会⼀直进⾏尝试。如果CAS⻓时间⼀直不成功,可能会给CPU带来很⼤的开销。
  2. 对⼀个共享变量执⾏操作时,我们可以使⽤循环CAS的⽅式来保证原⼦操作,但是,对多个共享变 量操作时,循环CAS就⽆法保证操作的原⼦性,这个时候就可以⽤锁来保证原⼦性。
  3. 引出了ABA问题。

四 CAS会导致"ABA问题"

在这里插入图片描述
所谓ABA问题,就是CAS算法实现需要取出内存中某时刻的数据并在当下时刻⽐较并替换,这⾥存在⼀ 个时间差,那么这个时间差可能带来意想不到的问题。
⽐如,⼀个线程A 从内存位置Value中取出3,这时候另⼀个线程B 也从内存位置Value中取出3,并且线程B进⾏了⼀些操作将值变成了4,然后线程C⼜再次将值变成了3,这时候线程A进⾏CAS操作发现 内存中仍然是3,然后线程A操作成功。
尽管线程A的CAS操作成功,但是不代表这个过程就是没有问题的。在这里插入图片描述

有这样的需求,⽐如CAS,只注重头和尾,只要⾸尾⼀致就接受。
但是有的需求,还看重过程,中间不能发⽣任何修改,这就引出了

4.1.AtomicReference 原⼦引⽤。

AtomicInteger对整数进⾏原⼦操作,如果是⼀个POJO呢?可以⽤ AtomicReference来包装这个 POJO,使其操作原⼦化。

public class AtomicReferenceDemo {
    public static void main(String[] args) {
        User user1 = new User("Jack",25);
        User user2 = new User("Tom",21);
        User user3 = new User("Ros",28);
        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(user1);
        //CAS操作 主内存中的原始值user1和期望值user1比较相等,返回值为true且将主内存中的原始值修改为user2;
        System.out.println(atomicReference.compareAndSet(user1,user2)+"\t"+atomicReference.get());
        //CAS操作 主内存中的原始值user2和期望值user1比较不相等,返回值为false,不更新期望值;
        System.out.println(atomicReference.compareAndSet(user1,user3)+"\t"+atomicReference.get());
        //true	User(username=Tom, age=21)
        //false	User(username=Tom, age=21)
    }
}

4.2.ABA问题的解决(AtomicStampedReference 类似于时间戳)

在这里插入图片描述
使⽤ AtomicStampedReference类可以解决ABA问题。这个类维护了⼀个“版本号”Stamp,在进⾏CAS 操作的时候,不仅要⽐较当前值,还要⽐较版本号。只有两者都相等,才执⾏更新操作。
解决ABA问题的关键⽅法:
在这里插入图片描述
参数说明:
V expectedReference, 预期值引⽤
V newReference, 新值引⽤
int expectedStamp,预期值时间戳
int newStamp, 新值时间戳

4.2.1 ABA问题的产生演示

public class AtomicReferenceDemo2 {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    public static void main(String[] args) {
        System.out.println("========ABA问题的产生=========");
        new Thread(() -> {
            //CAS 主内存中的原始值100和期望值100比较相等,返回值为true且将主内存中的原始值修改为111;
            atomicReference.compareAndSet(100, 111);
            //CAS 主内存中的原始值111和期望值111比较相等,返回值为true且将主内存中的原始值修改为100;
            atomicReference.compareAndSet(111, 100);

        }, "t1").start();

        new Thread(() -> {
            //CAS
            System.out.println(atomicReference.compareAndSet(100, 2020) + "\t" + atomicReference.get());
        }, "t2").start();
    }

4.2.2.ABA问题的解决

package thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * ABA问题
 */
public class ABADemo {
    
    //带有时间戳的原子引用 (共享内存值100, 版本号为1)
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {

        System.out.println("=========ABA问题的解决===========");
        new Thread(() -> {
            //获取第一次的版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + stamp);

            //CAS 共享内存值100和期望值100比较相等,且共享内存时间戳和预期值时间戳相等;返回值为true且将共享内存值修改为111时间戳为2;
            try {//休眠一秒,模拟并发,给ThreadA预留时间启动。
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(
                    100,
                    111,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1

            );
            System.out.println(Thread.currentThread().getName() + "\t第二次版本号:" + atomicStampedReference.getStamp());

            //CAS 共享内存值111和期望值111比较相等,且共享内存时间戳和预期值时间戳相等;返回值为true且将共享内存值修改为100时间戳为3;
            atomicStampedReference.compareAndSet(
                    111,
                    100,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1
            );
            System.out.println(Thread.currentThread().getName() + "\t第三次版本号:" + atomicStampedReference.getStamp());


        }, "ThreadB").start();


        new Thread(() -> {
            //获取第一次的版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + stamp);

            //CAS 休眠3秒,与ThreadB时间差。模拟挂起;让ThreadB先执行,经过线程B的操作当前共享内存值为100,时间戳为3
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //共享内存值100和期望值100比较相等,但是共享内存时间戳3和预期值时间戳1不相等;返回值为false,不修改共享内存值和时间戳;
            boolean result = atomicStampedReference.compareAndSet(
                    100,
                    2020,
                    stamp,
                    stamp + 1
            );
            System.out.println(
                    Thread.currentThread().getName()
                            + "\t修改是否成功:" + result
                            + "\t当前最新的版本号:" + atomicStampedReference.getStamp()
                            + "\t当前最新的值:" + atomicStampedReference.getReference()
            );


        }, "ThreadA").start();
        //=========ABA问题的解决===========
        //ThreadB	第一次版本号1
        //ThreadA	第一次版本号1
        //ThreadB	第二次版本号:2
        //ThreadB	第三次版本号:3
        //ThreadA	修改是否成功:false	当前最新的版本号:3	当前最新的值:100
    }
}

在这里插入图片描述

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

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

相关文章

Eslint、Stylelint、Prettier、lint-staged、husky、commitlint【前端代码校验规则】

一、Eslint yarn add typescript-eslint/eslint-plugin typescript-eslint/parser eslint eslint-config-prettier eslint-config-standard-with-typescript eslint-plugin-import eslint-plugin-n eslint-plugin-prettier eslint-plugin-promise eslint-plugin-react eslint-…

实验四:搜索

实验四&#xff1a;搜索 1.填格子 题目描述 有一个由数字 0、1 组成的方阵中&#xff0c;存在一任意形状的封闭区域&#xff0c;封闭区域由数字1 包围构成&#xff0c;每个节点只能走上下左右 4 个方向。现要求把封闭区域内的所有空间都填写成2 输入要求 每组测试数据第一…

Provisioning Edge Inference as a Service via Online Learning 阅读笔记

通过在线学习提供边缘推理服务 一、论文研究背景、动机和主要贡献 研究背景 趋势&#xff1a;机器学习模型训练从中央云服务器逐步转移到边缘服务器 好处&#xff1a; 与云相比&#xff1a;a.低延迟 b.保护用户隐私&#xff08;数据不会上传到云&#xff09;与on-device相…

如何理解元数据、数据元、元模型、数据字典、数据模型这五个的关系?如何进行数据治理呢?数据治理该从哪方面入手呢?

如何理解元数据、数据元、元模型、数据字典、数据模型这五个的关系&#xff1f;如何进行数据治理呢&#xff1f;数据治理该从哪方面入手呢&#xff1f;导读一、数据元二、元数据三、数据模型四、数据字典五、元模型导读 请问元数据、数据元、数据字典、数据模型及元模型的区别…

数仓治理之数据梳理

目录 1.定义 2.用途作用 3.实施方法 3.1自上而下 3.1.1数据域梳理 3.1.2数据主题梳理 3.1.3 数据实体梳理 3.1.4设计数据模型 3.1.5优点 3.1.5缺点 3.2自下而上 3.2.1需求分析 3.2.2展现 3.2.3分析逻辑 3.2.4数据建模 3.2.5优点 3.2.6缺点 1.定义 “数据梳理”即对…

SpringBoot 如何保证接口安全?

为什么要保证接口安全对于互联网来说&#xff0c;只要你系统的接口暴露在外网&#xff0c;就避免不了接口安全问题。 如果你的接口在外网裸奔&#xff0c;只要让黑客知道接口的地址和参数就可以调用&#xff0c;那简直就是灾难。举个例子&#xff1a;你的网站用户注册的时候&am…

【云原生kubernetes】k8s数据存储之Volume使用详解

目录 一、什么是Volume 二、k8s中的Volume 三、k8s中常见的Volume类型 四、Volume 之 EmptyDir 4.1 EmptyDir 特点 4.2 EmptyDir 实现文件共享 4.2.1 关于busybox 4.3 操作步骤 4.3.1 创建配置模板文件yaml 4.3.2 创建Pod 4.3.3 访问nginx使其产生访问日志 4.3.4 …

I.MX6ULL_Linux_系统篇(27) 系统烧录工具

前面我们已经移植好了 uboot 和 linux kernle&#xff0c;制作好了根文件系统。但是我们移植都是通过网络来测试的&#xff0c;在实际的产品开发中肯定不可能通过网络来运行&#xff0c;因此我们需要将 uboot、 linux kernel、 .dtb(设备树)和 rootfs 这四个文件烧写到板子上的…

Nginx学习 (2) —— 虚拟主机配置

文章目录虚拟主机原理域名解析与泛域名解析&#xff08;实践&#xff09;配置文件中ServerName的匹配规则技术架构多用户二级域名短网址虚拟主机原理 为什么需要虚拟主机&#xff1a; 当一台主机充当服务器给用户提供资源的时候&#xff0c;并不是一直都有很大的用户量&#…

数据库面试题总结——DBA面试battle指南

目录 前言 数据库复制 oracle和pg的同步原理 mysql的同步原理 mysql的GTID 主从架构如何保证数据不丢失 oracle的保护模式 pg的日志传输模​​​​​​​式 mysql同步模式 从库只读 oracle的只读 pg的只读 mysql的只读 索引结构和寻迹 B树索引 索引寻迹 绑定执…

nacos源码入门

nacos官方文档地址&#xff1a;nacos官方文档 Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称&#xff0c;一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 简单来说&#xff0c;nacos就是一个注册中心、配置中心&#xff0…

灯具照明行业MES系统,助力企业实现数字化转型

灯具照明行业在制造领域&#xff0c;是典型的高科技离散生产制造模式&#xff0c;大部分企业都设置&#xff1a;电源组件、光源组件、或光电一体组件 &#xff0c;工艺以SMT、DIP等。 灯罩主要采用吸塑工艺及模具加工&#xff1b;其它金属的面盖、灯体、灯盒基本都是采用压铸、…

传送点遍历分析

由于《天涯明月刀》的地图较大&#xff0c;所以每个地图中会分布很多的传送点&#xff0c;而这些传送点都可以在访问过地图之后以“御风神行”这类技能进行传送。为了能够很好的利用这类技能&#xff0c;提高外挂的效率&#xff0c;传送点的遍历是必不可少的。 首先找一个可以…

代码随想录算法训练营第七天|454.四数相加II 、 383. 赎金信 、 15. 三数之和 、18. 四数之和

454.四数相加II 454.四数相加II介绍给你四个整数数组 nums1、nums2、nums3 和 nums4 &#xff0c;数组长度都是 n &#xff0c;请你计算有多少个元组 (i, j, k, l) 能满足&#xff1a;思路因为是存放在数组里不同位置的元素&#xff0c;因此不需要考虑去重的操作&#xff0c;而…

深度学习算法简要总结系列

今天突发奇想&#xff0c;准备一个总结系列&#xff0c;以备面试只需&#xff0c;嘿嘿&#xff0c;忘了就回来看看&#xff0c;以框架流程为主&#xff0c;不涉及细节、 点云 pointnet 代码仓库 https://github.com/yanx27/Pointnet_Pointnet2_pytorch 参考博客 论文阅读笔记 …

java单元测试批处理数据模板【亿点点日志配合分页以及多线程处理】

文章目录引入相关资料环境准备分页查询处理&#xff0c;减少单次批量处理的数据量级补充亿点点日志&#xff0c;更易观察多线程优化查询_切数据版多线程_每个线程都分页处理引入 都说后端开发能顶半个运维&#xff0c;我们经常需要对大量输出进行需求调整&#xff0c;很多时候…

Umi + React + Ant Design Pro 项目实践(一)—— 项目搭建

学习一下 Umi、 Ant Design 和 Ant Design Pro 从 0 开始创建一个简单应用。 首先&#xff0c;新建项目目录&#xff1a; 在项目目录 D:\react\demo 中&#xff0c;安装 Umi 脚手架&#xff1a; yarn create umi # npm create umi安装成功&#xff1a; 接下来&#xff0c;…

《OpenGL宝典》--纹理

文章目录创建并初始化纹理创建纹理更新纹理数据纹理目标和类型从着色器中读取纹理数据采样器类型使用texelFetch内置函数从着色器读取纹理使用texture&#xff08;&#xff09;函数从着色器读取纹理获取更多信息控制纹理数据的读取方式使用采样器对象存储采样器包装和过滤模式的…

AVL树的介绍和实现

我们知道&#xff0c;二叉搜索树是会出现单向的。单向在查找时效率是非常低的&#xff0c;时间复杂度会退化成O(N)&#xff0c;而AVL树就是解决这个问题。 文章目录1. AVL 树1.1 AVL树的概念1.2 AVL树节点的定义1.3 插入后的平衡因子1.4 AVL树的旋转1.4.1 右右&#xff1a;左单…

JavaScript 循环实例集合

文章目录JavaScript 循环实例集合For 循环循环输出 HTML 标题While 循环Do while 循环break 语句continue 语句使用 For...In 声明来遍历数组内的元素JavaScript 循环实例集合 For 循环 源码 <!DOCTYPE html> <html> <head> <meta charset"utf-8&q…