JUC-16. CAS

2023-10-30

想了解更多JUC的知识——JUC并发编程合集

1. CAS的概述

  • CAS的全称为Compare-And-Swap(比较并交换),它是一条CPU并发原语,比较工作内存值(预期值)和主物理内存的共享值是否相同,相同则执行规定操作,否则继续比较直到主内存和工作内存的值一致为止。这个过程是原子的
    • AtomicInteger类主要利用CAS(compare and swap)+volatilenative方法来保证原子操作,从而避免synchronized的高开销,执行效率大为提升。

  • CAS并发原语体现在Java语言中,就是sun.misc包下的UnSafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我实现 CAS汇编指令。这是一种完全依赖于硬件功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS是一条原子指令,不会造成所谓的数据不一致的问题

  • 案例

    public class AtomicIntegerDemo {
    	public static void main(String[] args) {
    	        AtomicInteger atomicInteger=new AtomicInteger(5);
    	        System.out.println(atomicInteger.compareAndSet(5, 20) +"\t"+atomicInteger.get()); //true 20
    	        System.out.println(atomicInteger.compareAndSet(5, 22) +"\t"+atomicInteger.get()); //false 20
    	}
    }
    

2. UnSafe类

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

    • UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务

  • 变量ValueOffset,便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的

  • volatile修饰的变量value,保证了多线程之间的可见性

  • Unsafe (不是指线程不安全,是指过于底层,不建议编程人员直接使用)对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得。LockSupport的park方法,cas相关的方法底层都是通过Unsafe类来实现的。

3. CAS的缺点

  1. 循环时间长开销很大

    .

    • 在getAndAddInt方法执行的时候,有个do-while循环,使用的是自旋锁,如果CAS失败,就会一直尝试,直到成功。如果长时间失败,可能会给CPU带来很大的开销。
  2. 只能保证一个共享变量的原子性

    • 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作
    • 对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用来保证原子性
  3. ABA问题的产生

    • 比如一个线程A从内存位置V中取出 i,这时候另一个线程B也从内存中取出 i,并且线程B进行了一些操作将值变成了 j,然后线程B又将V位置的数据变成i ,这时候线程A进行CAS操作发现内存中仍然是i ,然后线程A操作成功。尽管线程A的CAS操作成功,但是不代表这个过程就是没问题的

      .

      • 线程A:期望值是1,新值为2;
      • 线程B:两个操作:
        • 期望值是1,变成3
        • 期望值是3,变成1
      • 所以对于线程A来说,i 的值还是1,所以就出现了问题,骗过了线程A;

4. 解决ABA问题

  • 解决ABA问题,对应的思想:就是使用了乐观锁,即带版本号的原子操作

  • 解决方案:使用AtomicStampedReference每修改一次都会有一个版本号

    注意:

    Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。

    img

    Interger对-128~127的缓存这个范围才有效,不在这个范围comareAndSet会一直返回false。

  • 解决案例

    public class ABATest {
        /**
         * AtomicStampedReference
         * 注意:如果泛型是一个包装类,注意对象的引用问题(正常在业务操作,这里面比较的都是一个个对象)
         *  第一个参数:初始值
         *  第二个参数:初始版本号
         */
        static AtomicStampedReference<Integer> atomicReference = new
                AtomicStampedReference<>(100, 1);
        public static void main(String[] args) {
    
            new Thread(() -> {
                //获得版本号
                System.out.println("a1===version-" + atomicReference.getStamp());
                //暂停2s
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //第一次修改,将版本号+1
                /**
                 * atomicReference.compareAndSet()
                 * 参数1:期待值  参数2:新值
                 * 参数3:期待版本号    参数4:新版本号
                 */
                atomicReference.compareAndSet(100,101,
                        atomicReference.getStamp(),atomicReference.getStamp() + 1);
                System.out.println("a2===version-" + atomicReference.getStamp());
                //第二次修改,将版本号再+1
                atomicReference.compareAndSet(101,100,
                        atomicReference.getStamp(),atomicReference.getStamp() + 1);
                System.out.println("a3===version-" + atomicReference.getStamp());
            },"A").start();
    
            new Thread(() -> {
                //获得版本号
                System.out.println("b1===version-" + atomicReference.getStamp());
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicReference.compareAndSet(100,101,
                        atomicReference.getStamp(),atomicReference.getStamp() + 1);
                System.out.println("b2===version-" + atomicReference.getStamp());
            },"B").start();
        }
    }
    

    输出结果:

    .

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JUC-16. CAS 的相关文章

随机推荐

  • linux下grpc安装编译,gRPC的C++编译及简单使用

    grpc的编译及简单使用 1 grpc相关参考文档 2 使用cmake编译安装gRPC 本文下载grpc是使用大陆外服务器下载grpc项目源码v1 32 X分支及其子模块submodule 总共1G左右 下载用时5分钟左右 大陆内用户可能首
  • 系统架构主题之五:软件系统建模方法及其应用

    前面我们梳理了需求分析的相关内容 完成需求分析后 会输出指导软件开发的需求规格说明书 有了该文档的支持 下一步就是系统设计阶段 用于将软件需求的内容转换为可指导软件开发的概要设计和详细设计文档 下面我们从理论和实践上看看如果做系统设计 1
  • Centos7.6 安装最新k8s v1.24+containerd+calico

    安装最新k8s v1 24 containerd calico 开始之前 部署文件位置 端口开放 k8s中需要开放的端口 calico网络插件需要开放的端口 所有服务器都要做的操作 1 升级系统内核 CentOS 7 CentOS 8 2
  • 04_Nginx_从url中获取参数

    04 Nginx 从url中获取参数 1 导读 2 代码示例 3 实验截图 1 导读 需要从url中获取到想要的参数 特此记录方式 2 代码示例 使用的是ngx http request t结构体中的args参数 printf n char
  • 2022年「博客之星」 无知的人_的程序人生

    这是 2022 博客之星 的竞选帖子 请你在这里增加其他内容 包括但不限于 你这一年的收获 感悟 对CSDN 产品的反馈和 2023 年的希望 参考 https blog csdn net SoftwareTeacher article d
  • MYSQL查询当前表存在哪些索引

    查看表存在的索引 show index from table name 表名 结果列表中各字段的含义 Non unique 如果索引不能包括重复词 则为0 如果可以 则为1 Key name 索引名称 Seq in index 索引中的列序
  • TIKTOK视频:视频内容打造需要注意的几点 抓住流量密码

    TIKTOK视频 视频内容打造需要注意的几点 抓住流量密码 大家好 我是项柚 一个专注于讨论TikTok玩法的跨境电商自媒体人 每天不断输出干货给需要的朋友 大家都知道 欧美跨境市场已经被认为是 红海 很多人已经凭着一股冲劲凭着一边做一边学
  • mybatis plus 常用方法

    学习链接 简介 MyBatis Plus 一 分页 创建分页实体 Page
  • 文盘Rust -- 给程序加个日志

    日志是应用程序的重要组成部分 无论是服务端程序还是客户端程序都需要日志做为错误输出或者业务记录 在这篇文章中 我们结合log4rs聊聊rust 程序中如何使用日志 log4rs类似java生态中的log4j 使用方式也很相似 log4rs中
  • 基于SoC FPAG实现手写体识别(HLS编译的全连接算子)

    基于SoC FPAG实现手写体识别 HLS编译的全连接算子 点击操作手册下载 完整代码 1 HLS的代码 2 SoC EDS 中 eclipse 测试代码 由于流程过多 这里采用pdf文件下载的方式 点击操作手册下载 链接 https pa
  • 北京大学肖臻老师《区块链技术与应用》公开课笔记-BTC

    本笔记为学习期间对主要知识和逻辑的记录 根据课程内容分为BTC和ETH两篇 本篇为BTC部分 北京大学肖臻老师 区块链技术与应用 公开课笔记 ETH 文章目录 01 课程简介 02 BTC 密码学原理 03 BTC 数据结构 04 BTC
  • javascript ES5中 foreach()遍历方法

    forcach array forEach function currentValue index arr currentValue 数组当前项的值 index 数组当前项的索引 可选 arr 数组对象本身 filter 方法创建一个新的数
  • Unable to launch the IIS Express Web server 问题之解决 - [Visual Studio 2015]

    背景 Visual Studio 2015 在 Debug 模式下调试失败 报错如下图所示 解决办法 删除解决方案下 vs config 文件夹内的这个配置文件 再关闭并重新运行解决方案即可进行调试
  • 清除SVN版本信息

    echo on color 2f mode con cols 80 lines 25 REM echo Deleting all svn please wait rem Delete svn in current and sub direc
  • LeetCode之Count Binary Substrings(Kotlin)

    问题 Give a string s count the number of non empty contiguous substrings that have the same number of 0 s and 1 s and all
  • 如何搭建C语言环境

    以下文章来源于 公 众 号开源电子网 读取更多技术文章 请扫码关注 如何搭建C语言环境 前言 C语言作为嵌入式开发的必备掌握技能 嵌入式能力的提升速度很大程度在于C语言的掌握能力 正所谓 工欲善其事 必先利其器 学习C语言 第一件动手的事情
  • 【餐厅点餐平台|一】项目描述+需求分析

    餐厅点餐平台导航 餐厅点餐平台 一 项目描述 需求分析 https blog csdn net weixin 46291251 article details 126414430 餐厅点餐平台 二 总体设计 https blog csdn
  • 大数据系列——Redis部署及应用

    Redis有四种部署方式 分别为单机模式 主备模式 哨兵模式 集群模式 其中单机模式比较简单 容量 处理能力有限 没有高可用 主备模式和哨兵模式本质和单机模式一样 只是主备模式保证数据高可用 哨兵模式保证数据和服务的高可用 集群模式是将数据
  • 为什么宏定义和函数定义运行结果不一样?

    函数定义 include
  • JUC-16. CAS

    想了解更多JUC的知识 JUC并发编程合集 1 CAS的概述 CAS的全称为Compare And Swap 比较并交换 它是一条CPU并发原语 比较工作内存值 预期值 和主物理内存的共享值是否相同 相同则执行规定操作 否则继续比较直到主内