【JavaEE】CAS机制(比较并交换)

news2025/2/27 17:11:14

哈喽,大家好~我是你们的老朋友保护小周ღ,本期为大家带来的是 CAS (compare and swap) 比较并交换,CAS 是物理层次支持程序的原子操作, CAS 是一种完全不同于 synchronized 锁保证多线程安全问题的机制,可以用来进行无锁编程,讲述了 CAS 的概率,使用场景,以及优缺点,确定不来看看嘛~
更多精彩敬请期待:保护小周ღ *★,°*:.☆( ̄▽ ̄)/$:*.°★* ‘


一、什么是 CAS 

CAS (compare and swap) 比较并交换,CAS 是物理层次支持程序的原子操作。说起原子性,这就设计到线程安全问题,站在代码的层面为了解决多线程并发处理同一共享资源造成的线程安全问题,我们常常会使用 synchronized 修饰代码块,变量等,将程序背后的指令封装成原子性(事务的最小单位,不可再分),当一个线程执行 synchronized 修饰的代码块时获取指定对象的对象锁(一个对象只有一把锁),其他并发处理同一代码块的线程因无法获取对象锁就会进入阻塞等待对象锁释放,然后继续竞争对象锁,此时 synchronized 修饰的代码块就具有原子性,具有互斥性,不可抢占性。

Object locker = new Object; //使用第三方对象锁

synchronized(locker) { //尝试获取 locker 的对象锁,获取成功继续执行,失败线程阻塞等待
    //具有原子性的代码块
}
// 同步代码块执行完毕,自动释放对象锁,其他线程就可以获取对象锁

CAS 是CPU 物理层次支持的原子操作(一条指令).

CAS 操作包含三个操作数:

读取内存数据(V),预期原值(A)和新值(B)。

如果内存位置的值与预期原值相等,就将新值(B)更新到内存中,替换掉原内存数据,

如果内存位置的值与预期原值不相等,处理器不会做任何操作,

CAS 对数据操作后会返回一个 boolean 值判断是否成功。

以上指令集合,可以视为CPU 物理层次支持的一条指令,同样可以解决多线程并发处理同一共享资源造成的线程安全问题。

CAS 使用Java伪代码理解含义:

// address 原内存值
// expectValue 旧的预期值
// swapValue 需要修改的新值
// & 代表取地址,这里主要是理解这层含义,java 语法不支持
boolean CAS(address, expectValue, swapValue) {
    if (&address == expectedValue) {
       &address = swapValue;
       return true;
    }
    return false;
}

举个例子(多线程同时对同一共享数据进行处理引起的线程安全问题): 

 使用 CAS 后:

CAS 是一种完全不同于 synchronized 锁保证多线程安全问题的机制。

Java 伪代码方便理解上述例题操作大概是怎么运行的。

private int count = 0;

public int getAndIncrement() {
int oldValue = count;

  while ( CAS(value, oldValue, oldValue+1) != true) {
     oldValue = value;
  }
  return oldValue;
}

本来以上这样的操作站在代码角度不是原子性有多条指令, 但是 CAS 在硬件层面上支持一条指令完成这个操作, 所以也就变成原子的了。

根据以上事例、伪代码,我们会发现,当多线程企图对 count 进行自增时,同一时间内只有一个线程能成功修改数据,其他线程会因为一直不符合以下三条规定,而循环的访问内存,直到条件成立

读取内存数据(V),预期原值(A)和新值(B)。

1. 比较 A 与 V 是否相等。(比较)
2. 如果比较相等,将 B 写入 V。(交换),否则啥也不干
3. 返回操作是否成功。


 二、CAS 的缺点

1.多线程并发执行,同一单位时间内只有一个线程能处理数据成功,此时其他共同想处理 count 的线程循环的读取内存数据,进行CAS 判断, 这样就造成了 CPU 资源的消耗。

从某种意义上来说也提升了多线程处理数据的速度,如果 synchronized 锁在锁冲突严重的情况下,竞争锁失败的线程会进入阻塞等待(暂时不参与CPU 的调度执行),这样一来CPU 的工作效率会提升,但是释放锁的后,需要将等待的线程唤醒,这也是一笔开销,如果 synchronized 在锁冲突不严重的情况下,会采用 “自旋锁” 的方式来实现,自旋就是一直循环的尝试获取对象锁,所以当对象锁被释放,自旋线程能够保证第一时间拿到锁,这一点是不是跟 CAS 操作很像,没错,synchronized 的自旋属性就是根据 CAS 机制实现的,

synchronized 会根据当前锁竞争的程度,自适应的转化锁属性

2. CAS机制所保证的只是一个变量(一段数据的)的原子性操作,而不能保证整个代码块的原子性。比如需要保证多个变量共同进行原子性的更新,或者一段程序的逻辑,就不得不使用synchronized 修饰了。

3. BAB 问题

这个问题也是 CAS 机制中不符合常理的问题

CAS 机制的关键在于比较内存和寄存器的值,看看是否相同,就是通过这个比较,来判断内存是不是被修改过,如果对比的时候是相同的,但不代表内存中的值在内读取之前没有变动过。

例如: 有线程 A,B,C, 有整型变量 value = 10;

A 线程先将 value 值从内存中读取到 寄存器中,同一时刻,线程 B 将 value 值修改为 100,线程 C 又将 value 值 100 修改成了 10, 此时 线程 A  需要再从内存中读取一次 value 用来校验 value 是否被修改过,这个时候读取的是10 ,与寄存器中的值相等,value数据可以更新,站在线程A 的角度上看,value 的数据没有被修改,但是站在上帝视角来看,value 的值已经被线程 B、C 修改过了,万一对比的时候是相同的,但是不代表 value 值没有变动过,从 a  ->  b  - > a,这个过程中就有一定的概率会出现问题。

要解决ABA的问题Java也提供了AtomicStampedReference类供我们用就是加了个版本,比对的就是内存值 + ”版本号“是否一致,版本号约定数据一次只能单方向的变化(数值只能增加或者是减小),如果需要数据既能增加也能减小,这个时候可以引入另外一个版本号变量,约定版本号只能增加或者是减少,此时每次 CAS 对比两组数据的时候,不是对比数据本身,而是对比版本号,以版本号为基准,内存中的值每发生一次变化,版本号都+1;在进行CAS操作时,不仅比较内存中的值,也会比较版本号,只有当二者都没有变化时,CAS才能执行成功。


三、CAS 的应用

3.1 CAS 实现原子类

java使用Unsafe类来支持CAS操作,Unsafe 类在sun.misc 包下,不属于 Java 标准库,但是有很多Java 的标准库中使用 Unsafe 类开发,Unsafe类使Java代码拥有了像C语言的指针一样操作内存空间的能力(Unsafe里面的方法都是 native方法,可以访问本地C++实现库,JVM 底层逻辑就有 大量基于 C/C++ 实现的部分,也可以访问操作系统的底层,操作系统的底层也是 C/ C++ 是实现的),同时也带来了指针的问题,Unsafe类也提供了许多硬件级别的原子操作。

Java 标准库中 AtomicInteger 类就是一个提供原子操作的 Integer 的类,他的底层方式就是使用了Unsafe 类中的方法作为支持,也就是说同样使用了 CAS 机制。

public class Demo1 {
    public static void main(String[] args) {
        // 将期望值设置为 5
        AtomicInteger atomicInteger = new AtomicInteger(5);
        // 确定是否还是等于 5 ,如果匹配成功就将新值更新到内存,覆盖掉atomicInteger的值; 返回 boolean值
        System.out.println(atomicInteger.compareAndSet(5, 2020));
        System.out.println("data:" + atomicInteger.get());

        // 确定atomicInteger值是否还是等于 5,如果是更新为 2023
        System.out.println(atomicInteger.compareAndSet(5, 2023));
        System.out.println("data:" + atomicInteger.get());
    }
}

 AtomicInteger 类内部方法

AtomicInteger 类是一个可以在多线程环境中保证一致性的类,使用起来简单,可以大大提高多线程开发程序的安全性和可能性,是无锁编程的机制。


到这里,CAS (compare and swap) 机制的概念,优缺点,使用场景,博主已经分享完了,希望对大家有所帮助,如有不妥之处欢迎批评指正。

本期收录于博主的专栏——JavaEE,适用于编程初学者,感兴趣的朋友们可以订阅,查看其它“JavaEE基础知识”。

感谢每一个观看本篇文章的朋友,更多精彩敬请期待:保护小周ღ *★,°*:.☆( ̄▽ ̄)/$:*.°★* ‘

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

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

相关文章

Java基础——Stream流

(1)Stream流概述: 1.什么是Stream流? 用于简化集合和数组操作的API。结合了Lambda表达式。方便操作集合/数组的手段(集合/数组才是开发的目的)。2.体验Stream流的作用: import java.util.Arr…

高并发场景I/O优化

大家好,我是易安! Java I/O是一个众所周知的概念。它常被用于读写文件、实现Socket信息传输等操作,这些都是系统中最常见的与I/O相关的任务。 我们都了解,I/O的速度相较于内存速度较慢。在当前大数据时代背景下,I/O性能…

java足球体育新闻资讯发布系统ssh

为 本系统的功能目标分为以下几个模块:用户管理模块、足球新闻类别管理模块、足球新闻管理模块、留言管理模块和前台足球新闻浏览模块。 系统功能模块的划分,是在需求分析基础上进行的,是把具有复杂功能的系统通过设计分解为具有基本独立&…

【并发编程】AQS源码

ReentrantLock 互斥锁,可重入 AQS是可以支持互斥锁和共享锁的,这里只分析互斥锁的源码 加锁 公平锁和非公平锁 公平锁 final void lock() {acquire(1); //抢占1把锁.}// AQS里面的方法public final void acquire(int arg) { if (!tryAcquire(arg) &&acq…

MySQL:事务、索引、用户管理、备份、数据库设计(三大范式)

文章目录Day 03:一、事务1. 原则2. 测试实现二、索引1. 分类2. 创建索引3. 分析 sql 执行的状况4. 测试索引5. 索引原则三、数据库用户管理四、备份五、规范数据库设计1. 三大范式注意:Day 03: 一、事务 事务(transaction):要么…

含光热电站、有机有机朗肯循环、P2G的综合能源优化调度(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

『pyqt5 从0基础开始项目实战』04. 表格数据的初始化(保姆级图文)

目录导包和框架代码准备json数据文件表格数据接入1. 准备文件路径2. 读取json数据3. 将得到的json数据放入table设置单元格不可修改把数据中的数字转为映射内容完整代码总结欢迎关注 『pyqt5 从0基础开始项目实战』 专栏,持续更新中 欢迎关注 『pyqt5 从0基础开始项…

TiDB实战篇-备份恢复策略

简介 简要说明TiDB备份恢复策略。 备份的类型 热备 TiDB使用MVCC机制实现设备的。 冷备 需要停机备份。 温备 备份的时候只能读不能够写。 备份技术 逻辑备份 物理备份 物理备份的限制 基于复制的备份 复制恢复是最快的。(TiDB CDC,TiDB Binlog&#xff…

【C语言】函数详解(嵌套调用和链式访问、声明及定义、递归)

简单不先于复杂,而是在复杂之后。 目录 1.函数的嵌套调用和链式访问 1.1 嵌套调用 1.2 链式访问 2. 函数的声明和定义 2.1 函数声明 2.2 函数定义 3. 函数递归 3.1 什么是递归? 3.2 递归的两个必要条件 3.2.1 练习1(需要画图…

Spring Security实战(三)—— 自动登录与注销登录

目录 一、实现自动登录 1. 散列加密方案 2. 持久化令牌方案 二、注销登录 一、实现自动登录 自动登录是将用户的登录信息保存在用户浏览器的cookie中,当用户下次访问时,自动实现校验并建立登录态的一种机制。 Spring Security 提供了两种非常好的令牌&a…

C ++ 基础入门。加强变量、指针、结构体理解

1、 const放外面,值不可以改。只读 同理于指针 看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量 const 放外面,值不可以改 2、 所有的指针类型,包括结构体指针 double * int *都是和操作系统位数…

补充——spark RDD序列化和持久化

目录 RDD序列化 闭包检查: 序列化方法和属性 Kryo序列化框架: RDD持久化(RDD persistence) RDDCache缓存 RDD persist缓存 什么时候使用persist()? RDD CheckPoint 检查点 缓存和检查点区别 RDD序列化 闭包检查&#x…

JavaScript 的基础函数有哪些?

1、在 JavaScript 中将数组本地转换为对象 JavaScript 有一个原生函数 Object.fromEntries,可用于将任何输入数组转换为对象。 1.const anArray [ 2. [firstname, Paul], 3. [surname, Knulst], 4. [address, worldwide], 5. [role, Senior Engineer], 6. […

Java中的异常Exception和捕获,自定义异常

文章目录1. 异常概述1.1 什么是程序的异常1.2 异常的抛出机制1.3 如何对待异常2. Java异常体系2.1 Throwable2.2 Error 和 Exception2.3 编译时异常和运行时异常3. 常见的错误和异常3.1 Error3.2 运行时异常3.3 编译时异常4. 异常的处理4.1 异常处理概述4.2 捕获异常&#xff0…

springboot整合websocket

1.创建springboot项目&#xff0c;引入spring-boot-starter-websocket依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>全部依赖如下&#xff1a; &l…

JDBC之DAO层封装思想超详解

Mysql版本&#xff1a;8.0.26 可视化客户端&#xff1a;sql yog 编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 运行环境&#xff1a;win10 家庭中文版 jdk版本&#xff1a;1.8.0_361 目录一、DAO是什么&#xff1f;二、案例演示2.1 准备数据2.2 创建bean包2.3 建立DAO包2.2…

Houdini>RBD(搅拌大米效果)并导出FBX到unity

Houdini&#xff1e;RBD(搅拌大米效果) 效果展示&#xff1a; 动图录制软件&#xff1a;Cockos Incorporated | LICEcap 参考链接&#xff1a;导出除了ABC外&#xff0c;比较小的FBX文件用法 目录&#xff1a; 一、引用模型的处理&#xff1a; 1、大米 模型创建 多层复制 …

Mybatis(六)缓存

缓存是Mybatis中非常重要的特性&#xff0c;Mybatis的一级缓存基于SqlSession实现&#xff0c;二级缓存基于Mapper实现。 一、缓存的使用 一级缓存默认开启&#xff0c;Mybatis提供了一个配置参数localCacheScope来控制一级缓存的级别&#xff0c;该参数的取值可以是session、…

【机器学习】P10 从头到尾实现一个线性回归案例

这里写自定义目录标题&#xff08;1&#xff09;导入数据&#xff08;2&#xff09;画出城市人口与利润图&#xff08;3&#xff09;计算损失值&#xff08;4&#xff09;计算梯度下降&#xff08;5&#xff09;开始训练&#xff08;6&#xff09;画出训练好的模型&#xff08;…

参加Matlab与AI讲座:使用深度强化学习训练走路机器人观后感

时间&#xff1a;2023年4月12日&#xff0c;周三&#xff0c;天气晴 地址&#xff1a;大连理工大学研教楼303 前言&#xff1a;Matlab其实有很多功能&#xff0c;我们所用的只是最基础最简单的部分&#xff0c;例如矩阵计算&#xff0c;画图等等。 随着强化学习的发展&#xff…