Java——》CAS

news2024/11/29 20:46:16

推荐链接:
    总结——》【Java】
    总结——》【Mysql】
    总结——》【Redis】
    总结——》【Kafka】
    总结——》【Spring】
    总结——》【SpringBoot】
    总结——》【MyBatis、MyBatis-Plus】
    总结——》【Linux】
    总结——》【MongoDB】
    总结——》【Elasticsearch】

Java——》CAS

  • 一、概念
  • 二、参数
  • 三、结果
  • 四、使用场景
  • 五、底层实现
    • 1、Java:Unsafe类中的native方法
    • 2、C++:`unsafe.cpp中的`Unsafe_CompareAndSwapInt执行cmpxchg指令
    • 3、linux:atomic_linux_x86.inline.hpp中的cmpxchg使用lock指令
  • 六、基于CAS实现的类
  • 七、优点
  • 八、缺点
    • 1、ABA问题
      • (1)现象
      • (2)解决方案
      • (3)示例
    • 2、如果cas失败(自旋)次数过多占用CPU资源
      • (1)现象
      • (2)解决方案
    • 3、只能保证一个数据的安全
      • (1)现象
      • (2)解决方案
  • 九、CPU 实现原子指令
    • 1、通过总线锁定来保证原子性
    • 2、通过缓存锁定来保证原子性
  • 十、CAS保证原子性示例

一、概念

CAS = Compare And Swap = 比较并交换

  • 先比较一下值是否与预期值一致,如果一致,交换,返回true
  • 先比较一下值是否与预期值一致,如果不一致,不交换,返回false

    CAS是一种在多线程环境中进行操作的原子指令。它的作用是对内存中的某个位置进行一次“乐观”的读取,并根据读取值与期望值是否相同来决定是否对该内存位置进行更新。
    CAS 操作是一种无锁的写入方式,允许多个线程并发的访问内存,并且通常比加锁操作的性能要高。
    Java中基于Unsafe的类提供了对CAS的操作的方法,JVM会帮助我们将方法实现CAS汇编指令。
    通过cmpxchg指令实现,单核就采用cmpxchg,多核需要追加前缀lock指令保证只有一个线程在执行当前CAS。

二、参数

参数描述
内存位置(V)要进行操作的内存位置
期望值(E)将内存位置的值与期望值进行比较,如果相同则进行更新,否则不进行操作
更新值(U)当期望值与内存位置的值相同时,CAS 操作会将内存位置的值更新为更新值

三、结果

CAS 操作的结果是一个布尔值,表示操作是否成功。
当期望值与内存位置的值相同时,CAS 操作会成功并返回 true,否则返回 false。

四、使用场景

  1. 实现各种无锁的同步机制,例如计数器、队列和栈等。
  2. 线程数较少、等待时间短可以采用自旋锁进行CAS尝试拿锁,较于synchronized高效

五、底层实现

最终回答:先从比较和交换的角度去聊清楚,在Java端聊到native方法,然后再聊到C++中的cmpxchg的指令,再聊到lock指令保证cmpxchg原子性

1、Java:Unsafe类中的native方法

CAS在Java层面就是Unsafe类中提供的一个native方法,成功返回true,失败返回false,如果需要重试策略,自己实现。

native是直接调用本地依赖库C++中的方法

/`
 * 如果变量的值为预期值,则更新变量的值,该操作为原子操作,如果修改成功则返回true
 * 
 * 4个参数:哪个对象,哪个属性的内存偏移量,oldValue预期值,newValue最终值
 */
public final native boolean compareAndSwapObject(Object o, long offset,
                                                 Object expected,
                                                 Object x);

public final native boolean compareAndSwapInt(Object o, long offset,
                                              int expected,
                                              int x);

public final native boolean compareAndSwapLong(Object o, long offset,
                                               long expected,
                                               long x);

2、C++:unsafe.cpp中的Unsafe_CompareAndSwapInt执行cmpxchg指令

本地已经下载hospot源码:\hotspot\src\share\vm\prims\unsafe.cpp
直接官网查看hospot源码:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/prims/unsafe.cpp

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* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

image.png

3、linux:atomic_linux_x86.inline.hpp中的cmpxchg使用lock指令

本地已经下载hospot源码:\hotspot\src\os_cpu\linux_x86\vm\atomic_linux_x86.inline.hpp
直接官网查看hospot源码:http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/69087d08d473/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp

cmpxchg是汇编指令,CPU硬件底层就支持cmpxchg。
cmpxchgl本身是原子性操作(不能再拆分的指令),但并不能保证原子性,所以需要判断当前系统是否为多核处理器,如果是多核,就添加lock前缀。
lock指令可以理解为CPU层面的锁,一般锁的粒度就是缓存行级别的锁,当然也有总线锁,但是成本太高,CPU会根据情况选择。

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_MP();// 内联函数,用来判断当前系统是否为多处理器(如果当前系统是多处理器返回1,否则返回0)
// __asm__代表汇编,直接操作硬件,执行cmpxchgl指令
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" // 如果当前系统是多处理器(即mp值为1),则为cmpxchg指令添加lock前缀,否则不加lock前缀
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}

image.png

六、基于CAS实现的类

参考链接:
Java——》Unsafe源码分析
Java——》AtomicInteger源码分析

  • AtomicInteger
  • LongAdder
  • ReentrantLock

image.png

七、优点

什么是非阻塞式的呢?其实就是一个线程想要获得锁,对方会给一个回应表示这个锁能不能获得。在资源竞争不激烈的情况下性能高,相比synchronized重量锁会进行比较复杂的加锁,解锁和唤醒操作。

CAS是一种乐观锁,而且是一种非阻塞的轻量级的乐观锁,不会挂起线程。

八、缺点

1、ABA问题

ABA不一定是问题!因为一些只存在 ++,–的这种操作,即便出现ABA问题,也不影响结果!

(1)现象

image.png

(2)解决方案

在修改value同时追加 版本号

(3)示例

JUC下提供的AtomicStampedReference(同时判断值和版本号)
image.png

public static void main(String[] args) {
    AtomicStampedReference<String> reference = new AtomicStampedReference<>("AAA",1);

    String oldValue = reference.getReference();
    int oldVersion = reference.getStamp();

    boolean b = reference.compareAndSet(oldValue, "B", oldVersion, oldVersion + 1);
    System.out.println("修改1版本的:" + b);

    boolean c = reference.compareAndSet("B", "C", 1, 1 + 1);
    System.out.println("修改2版本的:" + c);
}

2、如果cas失败(自旋)次数过多占用CPU资源

(1)现象

while循环,cas一直没成功,会一直进行自旋,会额外的占用大量的CPU资源
image.png

Q:为什么会占用CPU资源?
A:因为CAS不会挂起线程,会让CPU一致调度当前线程执行CAS直到成功。

(2)解决方案

不同场景有不同的处理方案

方案特点具体实现
synchronized的实现方式自适应自旋锁如果cas失败(自旋)次数过多(超过指定次数),就挂起线程(WAITING),避免占用CPU过多的资源
LongAdder的实现方式分段锁在CAS一次失败后,将这个操作暂存起来,后面需要获取结果时,将暂存的操作全部执行,再返回最后的结果

Q:LongAdder?
A:基于类似 分段锁 的形式去解决(要看业务,有限制的)
传统的AtmoicLong是针对内存中唯一的一个值去++
现在的LongAdder在内存中搞了好多个值,多个线程去加不同的值,如果自增失败,将失败的信息添加到Cell[],当需要结果时,将所有值累加再返回。

3、只能保证一个数据的安全

(1)现象

无法像synchronized一样锁住一段代码

(2)解决方案

ReentrantLock基于AQS实现,AQS基于CAS的方式实现了锁的效果。

九、CPU 实现原子指令

CPU 实现原子指令有 2 种方式:

  • 1、通过总线锁定来保证原子性
  • 2、通过缓存锁定来保证原子性

1、通过总线锁定来保证原子性

所谓总线锁定就是使用处理器提供的一个 LOCK# 信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占共享内存。但是该方法成本太大。因此有了下面的方式。

2、通过缓存锁定来保证原子性

所谓 缓存锁定 是指内存区域如果被缓存在处理器的缓存行中,并且在 Lock 操作期间被锁定,那么当他执行锁操作写回到内存时,处理器不在总线上声言 LOCK# 信号,而是修改内部的内存地址,并允许他的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改两个以上处理器缓存的内存区域数据(这里和 volatile 的可见性原理相同),当其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效。

注意:有两种情况下处理器不会使用缓存锁定:

  1. 当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行时,则处理器会调用总线锁定。
  2. 有些处理器不支持缓存锁定,对于 Intel 486 和 Pentium 处理器,就是锁定的内存区域在处理器的缓存行也会调用总线锁定。

十、CAS保证原子性示例

private static AtomicInteger count = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            count.incrementAndGet();
        }
    });
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            count.incrementAndGet();
        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(count);
}

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

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

相关文章

【MongoDB】索引 – 文本索引

一、准备工作 这里准备一些数据 db.books.insertMany([{_id: 1, name: "Java", description: "java 入门图书", translation: [{ language: "english", description: "java basic book" }]},{_id: 2, name: "C", descript…

七年老程序员的九十月总结:上热榜、社区分享

作者简介&#xff1a;张拭心&#xff0c;Android GDE&#xff08;Google Developer Expert&#xff09;&#xff0c;CSDN 博客专家如果感觉博主的文章还不错的话&#xff0c;请三连&#xff08;点赞留言转发&#xff09;支持一下博主哦联系方式&#xff1a;godblesszsx&#xf…

第二证券:政策效应逐步显现 A股修复行情有望持续演绎

上星期&#xff0c;A股商场延续企稳反弹的态势&#xff0c;上证指数震荡上涨0.43%&#xff1b;沪深两市日均成交额回升至8700亿元左右&#xff1b;北向资金近一个月初次转为周净买入5.57亿元。 安排观点一起认为&#xff0c;在稳增加、稳预期相关政策持续发力&#xff0c;上市…

linux 查看驱动模块(lsmod)和 驱动设备(ls /dev)

一、lsmod lsmod 命令&#xff0c;用于列出当前 linux 系统中加载的模块。当驱动开发人员编写好驱动代码&#xff0c;并生成驱动代码对应的驱动模块后&#xff0c;可以通过 insmod xxx.ko 将驱动模块&#xff08;.ko&#xff09;加载到 linux 操作系统中。最后&#xff0c;通过…

基于ruoyi框架项目-部署到服务器上

基于ruoyi框架项目-部署到服务器上 文章目录 基于ruoyi框架项目-部署到服务器上1.前端vue编译&#xff0c;后的dist下内容打包&#xff08;前后端分离版本需要&#xff09;2.后端打包成jar包&#xff08;如果是thymeleaf仅需打包jar&#xff09;3.上传到服务器目录下4. docker部…

什么是MySQL的执行计划(Explain关键字)?

什么是Explain Explain被称为执行计划&#xff0c;在语句之前增加 explain 关键字&#xff0c;MySQL 会在查询上设置一个标记&#xff0c;模拟MySQL优化器来执行SQL语句&#xff0c;执行查询时&#xff0c;会返回执行计划的信息&#xff0c;并不执行这条SQL。&#xff08;注意&…

SAM 微调在医学上的尝试

1、2023下半年 1、 UNet与SAM结合的正确的道路SAMUS,一路SOTA没对手&#xff01; https://github.com/xianlin7/SAMUS 2、 本文提出 SonoSAM&#xff1a;一种用于分割超声图像上感兴趣对象的快速基础模型。 https://zhuanlan.zhihu.com/p/663988684 未开源 绿色是预测的&…

周大福开启“超越时光”天然钻石之旅,将非常钻石领入日常人生

&#xff08;2023年11月6日&#xff09;创立于1929年的周大福凭借独具匠心的设计、超凡卓越的品质和历史悠久的底蕴&#xff0c;成为了中国传统文化与现代审美相融合的知名珠宝品牌。周大福不仅在黄金珠宝领域声名显赫&#xff0c;在天然钻石方面也拥有独树一帜的专业实力。早在…

【广州华锐互动】3D全景虚拟旅游在文旅行业的应用场景

随着科技的不断发展&#xff0c;3D全景虚拟旅游正在成为一种新兴的旅游体验方式&#xff0c;它可以帮助旅游者更加深入地了解旅游信息&#xff0c;提升旅游体验。下面我们将详细介绍3D全景虚拟旅游可以应用于哪些场景。 一、旅游规划 3D全景虚拟旅游可以帮助旅游者更加直观地进…

Linux 本地Yearning SQL审核平台远程访问

文章目录 前言1. Linux 部署Yearning2. 本地访问Yearning3. Linux 安装cpolar4. 配置Yearning公网访问地址5. 公网远程访问Yearning管理界面6. 固定Yearning公网地址 前言 Yearning 简单, 高效的MYSQL 审计平台 一款MYSQL SQL语句/查询审计工具&#xff0c;为DBA与开发人员使用…

CDN加速:国内外价格与企业云服务最佳搭配方案

随着互联网的快速发展&#xff0c;CDN&#xff08;内容分发网络&#xff09;已经成为了企业提供高质量、高速度内容传递的不可或缺的工具。CDN通过将内容分发到离用户更近的服务器上&#xff0c;提高了网站性能&#xff0c;减少了加载时间&#xff0c;改善了用户体验。在本文中…

建表时如何合理选择字段类型

前言 我们在建表的时候关于字段类型的选择会有这么几类人&#xff1a; 严谨型 严格调研每个字段可能的大小&#xff0c;然后根据不同字段类型的限制&#xff0c;进行选择&#xff0c;这一类人在创建关系型数据表的时候是没有问题的。图自己省事型 把所有字段都设置为String&a…

硬件知识积累 共模电感的介绍

1. 基础知识了解 1. 共模电流(Common Mode Current)&#xff1a; 解释1&#xff1a;共模电流是不存在传导或有线回流通路的高频电流&#xff0c;共模电流与位移电流&#xff08;传导线路周边变化的电场&#xff09;一同组成环路实现电流闭合。 解释2: 共模电流是指通过电路的两…

GD32单片机远程升级下载,手机在线升级下载程序,GD32在线固件下载升级,手机下载程序固件方法

GD32、STM32单片机&#xff0c;是我们最常见的一种MCU。通常我们在使用STM32单片机都会遇到程序在线升级下载的问题。 GD32/STM32单片机的在线下载通常需要以下几种方式完成&#xff1a; 1、使用ST/GD提供的串口下载工具&#xff0c;本地完成固件的升级下载。 2、自行完成系统B…

自定义拖拽列表

效果图 DataAnalysis.vue <template><div class"app-container"><div class"operate"><el-select class"t_select" v-model"templateName" clearable placeholder"模版" size"default" cle…

电脑监控软件安装教程(30秒钟教会你快速安装)

在现代化办公中&#xff0c;如果有需要安装电脑监控软件的可以参考下面这篇文章&#xff1a; 一、准备阶段 可以自行到官网获取安装包&#xff0c;比如去域之盾官网&#xff0c;即可通过客服获取试用包。 二、操作阶段 1、这个就是安装包&#xff0c;点击运行。 2、这个是正…

Postgresql数据类型-数字类型

PostgreSQL支持的数字类型有整数类型、用户指定精度类型、浮点类型、serial类型。 PostgreSQL支持的数字类型如表所示 smallint、integer、bigint都是整数类型&#xff0c;存储一定范围的整数&#xff0c;超出范围将会报错。smallint存储2字节整数&#xff0c;字段定义时可写成…

大模型使用技巧

目录 1、文心一言&#xff1a;https://yiyan.baidu.com/ 2、智谱清言 ChatGLM&#xff1a;https://chatglm.cn/ 3、百川智能&#xff1a;https://www.baichuan-ai.com/ 4、科大讯飞星火大模型&#xff1a;https://xinghuo.xfyun.cn 5、字节跳动 AI 豆包 https://www.do…

idea2023 PoJie以后无法修改内存无效,heap内存设置

1. 打开电脑环境变量 2. 找到对应pojie文件 vmoptions目录 3. 修改这个文件 添加或者修改配置 -Xms128m -Xmx8192m4. 重启idea 修改成功

史上最全,从初级测试到高级测试开发面试题汇总,冲击大厂年50w+

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 接口测试面试相关…