Java——》CAS

推荐链接:
    总结——》【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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue Vuex模块化编码

正常写vuex的index的时候如果数据太多很麻烦&#xff0c;如有的模块是管理用户信息或修改课程等这两个是不同一个种类的&#xff0c;如果代码太多会造成混乱&#xff0c;这时候可以使用模块化管理 原始写法 如果功能模块太多很乱 import Vue from vue import Vuex from vuex …

京东数据分析:2023年9月京东打印机行业品牌销售排行榜

鲸参谋监测的京东平台9月份打印机市场销售数据已出炉&#xff01; 鲸参谋数据显示&#xff0c;今年9月&#xff0c;京东平台打印机的销量为60万&#xff0c;环比增长约32%&#xff0c;同比下滑约25%&#xff1b;销售额为5亿&#xff0c;环比增长约35%&#xff0c;同比下滑约29%…

[架构之路-244]:目标系统 - 设计方法 - 软件工程 - 软件开发方法:结构化、面向对象、面向服务、面向组件的开发方法

目录 前言&#xff1a; 一、概述: 软件聚合的程度由简单到复杂 二、主要开发方法详见 2.1 结构化的开发方法 2.2 面对对象的开发方法 2.3 面向服务的开发方法 2.4 面向组件的开发方法 三、不同开发方法比较 3.1 结构化开发方法 3.2 面向对象(OOP)开发方法 3.3 面向服…

vue中插槽slot

一、插槽-默认插槽 1.作用 让组件内部的一些 结构 支持 自定义 2.需求 将需要多次显示的对话框,封装成一个组件 3.问题 组件的内容部分&#xff0c;不希望写死&#xff0c;希望能使用的时候自定义。怎么办 4.插槽的基本语法 组件内需要定制的结构部分&#xff0c;改用&l…

Java版分布式微服务云开发架构 Spring Cloud+Spring Boot+Mybatis 电子招标采购系统功能清单

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及审…

19 款Agent产品工具合集

原文&#xff1a;19 款Agent产品工具合集 什么是Agent? 你告诉GPT完成一项任务&#xff0c;它就会完成一项任务。 如果你不想为GPT提出所有任务怎么办&#xff1f;如果你想让GPT自己思考怎么办&#xff1f; 想象一下&#xff0c;你创建了一个AI&#xff0c;你可以给它一个…

Scala语言使用Selenium库编写网络爬虫

目录 一、引言 二、环境准备 三、爬虫程序设计 1、导入必要的库和包 2、启动浏览器驱动程序 3、抓取网页内容 4. 提取特定信息 5. 数据存储和处理 四、优化和扩展 五、结语 一、引言 网络爬虫是一种自动抓取互联网信息的程序。它们按照一定的规则和算法&#xff0c;…

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

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

linux的美化工具 oh-my-zsh的安装与使用 神器工具

目录 1 安装zsh的环境2 安装 Oh My Zsh3 主题设置重新启动终端:关闭连接 在重新链接一下附加 -插件管理案例讲解看效果 Oh My Zsh 是一款基于 Zsh 的开源命令行工具&#xff0c;它提供了丰富的主题和插件&#xff0c;可以帮助用户更加高效地使用终端。本文将详细介绍 Oh My Zsh…

【Redis】Redis与SSM整合Redis注解式缓存Redis解决缓存问题

一&#xff0c;Redis与ssm整合 1.1 pom.xml配置 在pom.xml中配置相关的redis文件 redis文件&#xff1a; <redis.version>2.9.0</redis.version> <redis.spring.version>1.7.1.RELEASE</redis.spring.version><dependency><groupId>red…

第三章:人工智能深度学习教程-基础神经网络(第二节-ANN 和 BNN 的区别)

在本文中&#xff0c;我们将了解单层感知器及其使用 TensorFlow 库在Python中的实现。神经网络的工作方式与我们的生物神经元的工作方式相同。 生物神经元的结构 生物神经元具有三个基本功能 接收外部信号。 处理信号并增强是否需要发送信息。 将信号传递给目标细胞&#x…

Fourier分析导论——第4章——Fourier级数的一些应用(E.M. Stein R. Shakarchi)

第 4 章 傅里叶级数的一些应用 Fourier series and analogous expansions intervene very naturally in the general theory of curves and surfaces. In effect, this theory, conceived from the point of view of analysis, deals obviously with the study of arbitra…

ActiveMq学习⑦__ActiveMq协议

问题一、默认的61616端口如何更改&#xff1f; 问题二、你生产上的链接协议如何配置的&#xff1f;使用tcp吗&#xff1f; ActiveMQ 支持的client-broker 通讯协议有&#xff1a;TVP、NIO、UDP、SSL、Http(s)、VM。 其中配置TransportConnector 的文件在ActiveMQ 安装目录的co…

04【保姆级】-GO语言指针

之前我学过C、Java、Python语言时总结的经验&#xff1a; 先建立整体框架&#xff0c;然后再去抠细节。先Know how&#xff0c;然后know why。先做出来&#xff0c;然后再去一点点研究&#xff0c;才会事半功倍。适当的囫囵吞枣。因为死抠某个知识点很浪费时间的。对于GO语言&a…

二蛋赠书七期:《云原生数据中台:架构、方法论与实践》

前言 大家好&#xff01;我是二蛋&#xff0c;一个热爱技术、乐于分享的工程师。在过去的几年里&#xff0c;我一直通过各种渠道与大家分享技术知识和经验。我深知&#xff0c;每一位技术人员都对自己的技能提升和职业发展有着热切的期待。因此&#xff0c;我非常感激大家一直…

基于若依的ruoyi-nbcio流程管理系统仿钉钉流程json转bpmn的flowable的xml格式(简单支持发起人与审批人的流程)续

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 之前生产的xml&#xff0c;在bpmn设计里编辑有些内容不正确&#xff0c;包括审批人&#xff0c;关联表单等…

【蓝桥杯选拔赛真题13】C++最短距离 青少年组蓝桥杯C++选拔赛真题 STEMA比赛真题解析

C/C++最短距离 第十二届青少组蓝桥杯C++选拔赛真题 一、题目要求 1、编程实现 有一个居民小区的楼房全是一样的,并且按矩阵样式排列。其楼房的编号为 1,2,3……,当排满一行时,从下一行相邻的楼往反方向排号。 例如:小区为 3 行 6 列,矩阵排列方式: 要求:已知小区…

vue:如何实现通过判断数组中每个对象的其中一个属性,从而更改另一个属性的值

1、假设一个box为一个包含多个对象的数组&#xff0c;这个box数组可以在方法中定义也可以在data域中定义 let box [{ id: 1, status: 审批中 },{ id: 2, status: 已通过 },{ id: 3, status: 未通过 } ];2、在methods域中写一个方法遍历这个box数组判断每个对象中的status属性是…

Linux 服务器监控

服务器几乎与任何 IT 基础设施密不可分&#xff0c;Linux 是服务器兼容性最强的开源操作系统&#xff0c;因为它具有灵活性、一致性和安全性。大多数 Linux 服务器都设置了以下 Linux 操作系统的任何变体&#xff1a;Red Hat Enterprise Linux &#xff08;RHEL&#xff09;、D…

clickhouse安装与远程访问

安装&#xff08;本文以ubuntu系统为例&#xff09; 单节点设置​ 为了延迟演示分布式环境的复杂性&#xff0c;我们将首先在单个服务器或虚拟机上部署ClickHouse。ClickHouse通常是从deb或rpm包安装&#xff0c;但对于不支持它们的操作系统也有其他方法。 例如&#xff0c;…