CAS(Compare And Swap)

目录

CAS概念

乐观锁与悲观锁

ABA问题

Unsafe类

​编辑

原子类

基本类型原子类

原子引用类

原子数组

原子更新器类

原子累加器


CAS概念

CAS是Compare And Swap的缩写,中文翻译成:比较并交换,实现无锁并发时常用到的一种技术。它一般包含三个操作数——内存位置(V)、预期原值(A)及更新值(B)。某线程执行CAS操作的时候,将内存位置的值与预期原值比较:

  • 如果相匹配,那么处理器会自动将该位置值更新为新值
  • 如果不匹配,那么重新获取该内存位置的值,然后线程进行自旋,到下次循环才有机会执行。

 

底层原理:CAS 是CPU并发原语,底层是 lock cmpxchg 指令(X86 架构),在单核和多核 CPU 下都能够保证比较交换的原子性。

  • 程序是在单核处理器上运行,会省略 lock 前缀,单处理器自身会维护处理器内的顺序一致性,不需要 lock 前缀的内存屏障效果

  • 程序是在多核处理器上运行,会为 cmpxchg 指令加上 lock 前缀。当某个核执行到带 lock 的指令时,CPU 会执行总线锁定或缓存锁定,将修改的变量写入到主存,这个过程不会被线程的调度机制所打断,保证了多个线程对内存操作的原子性

CAS 特点:

  • CAS 体现的是无锁并发、无阻塞并发,线程不会陷入阻塞,线程不需要频繁切换状态(上下文切换,系统调用,用户态转换为内核态)

  • CAS 是基于乐观锁的思想

CAS 缺点:

  • 执行的是循环操作,如果比较不成功一直在循环,最差的情况某个线程一直取到的值和预期值都不一样,就会无限循环导致饥饿,使用 CAS 线程数不要超过 CPU 的核心数,采用分段 CAS 和自动迁移机制

  • 只能保证一个共享变量的原子操作

    • 对于一个共享变量执行操作时,可以通过循环 CAS 的方式来保证原子操作

    • 对于多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候只能用锁来保证原子性

  • 引出来 ABA 问题

乐观锁与悲观锁

悲观锁:

悲观锁就是我们常说的Synchronized 锁。对于悲观锁来说,它总是认为每次访问共享资源时会发生竞争,所以必须对每次对共享资源的操作加上锁,以保证临界区的程序同一时间只能有一个线程在执行。

乐观锁:

乐观锁又称为“无锁”,顾名思义,它是乐观派。乐观锁总是假设对共享资源的访问没有竞争的,线程可以不停地执行,无需加锁也无需等待。 乐观锁的一种实现方式 CAS 实现的 。

ABA问题

当一个主线程获取主内存值时,该内存值在写入主内存时已经被修改了 N 次,但是最终又改成原来的值,比如其他线程先把 A 改成 B 又改回 A,主线程使用CAS操作仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,这时 CAS 虽然成功,但是过程存在问题。

解决方法:使用AtomicStampedReference(加版本号解决ABA问题)

  • 构造方法:

    • public AtomicStampedReference(V initialRef, int initialStamp)

    • initialRef:初始值

    • initialStamp:初始版本号

  • 常用API:

  • public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)expectedReference:预期值、newReference:修改值 expectedStamp:期望版本号、expectedReference:操作成功之后的版本号。
  • public void set(V newReference, int newStamp):设置值和版本号

  • public V getReference():返回引用的值

  • public int getStamp():返回当前版本号

public static void main(String[] args) {AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference<>(100,1);int startStamp = atomicReference.getStamp();new Thread(() ->{int stamp = atomicReference.getStamp();atomicReference.compareAndSet(100, 101, stamp, stamp + 1);stamp = atomicReference.getStamp();atomicReference.compareAndSet(101, 100, stamp, stamp + 1);},"t1").start();
​new Thread(() ->{try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (!atomicReference.compareAndSet(100, 200, startStamp, startStamp + 1)) {System.out.println(atomicReference.getReference());//100System.out.println(Thread.currentThread().getName() + "线程修改失败");}},"t2").start();
}

执行结果:

100
t2线程修改失败

Unsafe类

Unsafe 是 CAS 的核心类,由于 Java 无法直接访问底层系统,需要通过本地(Native)方法来访问

Unsafe 类存在 sun.misc 包,其中所有方法都是 native 修饰的,都是直接调用操作系统底层资源执行相应的任务,基于该类可以直接操作特定的内存数据,其内部方法操作类似 C 的指针

可以看到AtomicInteger底层调用的就是Unsafe类中的compareAndSwapInt()方法

原子类

基本类型原子类

常用API

常见原子类:AtomicInteger、AtomicBoolean、AtomicLong

以AtomicInteger为例进行介绍

构造方法:

  • public AtomicInteger():初始化一个默认值为 0 的原子型 Integer

  • public AtomicInteger(int initialValue):初始化一个指定值的原子型 Integer

常用API:

方法作用
public final int get()获取 AtomicInteger 的值
public final int getAndIncrement()以原子方式将当前值加 1,返回的是自增前的值
public final int incrementAndGet()以原子方式将当前值加 1,返回的是自增后的值
public final int getAndSet(int value)以原子方式设置为 newValue 的值,返回旧值
public final int addAndGet(int data)以原子方式将输入的数值与实例中的值相加并返回 实例:AtomicInteger 里的 value

原子引用类

原子引用:对 Object 进行原子操作,提供一种读和写都是原子性的对象引用变量

原子引用类:AtomicReference、AtomicStampedReference、AtomicMarkableReference

AtomicReference 类:

  • 构造方法:AtomicReference<T> atomicReference = new AtomicReference<T>()

  • 常用 API:

    • public final boolean compareAndSet(V expectedValue, V newValue):CAS 操作

    • public final void set(V newValue):将值设置为 newValue

    • public final V get():返回当前值

public class AtomicReferenceDemo {public static void main(String[] args) {Student s1 = new Student(33, "z3");// 创建原子引用包装类AtomicReference<Student> atomicReference = new AtomicReference<>();// 设置主内存共享变量为s1atomicReference.set(s1);
​// 比较并交换,如果现在主物理内存的值为 z3,那么交换成 l4while (true) {Student s2 = new Student(44, "l4");if (atomicReference.compareAndSet(s1, s2)) {break;}}System.out.println(atomicReference.get());}
}
​
class Student {private int id;private String name;//。。。。
}

原子数组

原子数组类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

常用API:(以AtomicIntegerArray)

  • addAndGet(int i, int delta):以原子更新的方式将数组中索引为i的元素与输入值相加;
  • getAndIncrement(int i):以原子更新的方式将数组中索引为i的元素自增加1;
  • compareAndSet(int i, int expect, int update):将数组中索引为i的位置的元素进行更新
public static void main(String[] args) {AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);int t = 0;t = atomicIntegerArray.getAndSet(0,1);System.out.println(t+"\t"+atomicIntegerArray.get(0)); //0	1atomicIntegerArray.getAndIncrement(1);atomicIntegerArray.getAndIncrement(1);t = atomicIntegerArray.getAndIncrement(1);System.out.println(t+"\t"+atomicIntegerArray.get(1));//2	3}

原子更新器类

原子更新器类:AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater

利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常 IllegalArgumentException: Must be volatile type

常用 API:

  • static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> c, String fieldName):构造方法

  • abstract boolean compareAndSet(T obj, int expect, int update):CAS

public class UpdateDemo {private volatile int field;public static void main(String[] args) {AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(UpdateDemo.class, "field");UpdateDemo updateDemo = new UpdateDemo();fieldUpdater.compareAndSet(updateDemo, 0, 10);System.out.println(updateDemo.field);//10}
}

原子累加器

原子累加器类:LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator

LongAdder 和 LongAccumulator 区别:

相同点:

  • LongAddr 与 LongAccumulator 类都是使用非阻塞算法 CAS 实现的

  • LongAddr 类是 LongAccumulator 类的一个特例,只是 LongAccumulator 提供了更强大的功能,可以自定义累加规则,当accumulatorFunction 为 null 时就等价于 LongAddr

不同点:

  • 调用 casBase 时,LongAccumulator 使用 function.applyAsLong(b = base, x) 来计算,LongAddr 使用 casBase(b = base, b + x)

  • LongAccumulator 类功能更加强大,构造方法参数中

    • accumulatorFunction 是一个双目运算器接口,可以指定累加规则,比如累加或者相乘,其根据输入的两个参数返回一个计算值,LongAdder 内置累加规则

    • identity 则是 LongAccumulator 累加器的初始值,LongAccumulator 可以为累加器提供非0的初始值,而 LongAdder 只能提供默认的 0

常用API:

  • add():将当前value加x
  • increment():将当前的value加1
  • decrement():将当前的value减1
  • sum():放回当前值。注意:在没有并发更新value的情况下,sum会返回一个精确值,在存在并发的情况下,sum不保证返回精确值。
  • reset():将value重置为0,可用来代替重新new一个LongAdder,单词方法只可以在没有并发更新的情况下使用。
public static void main(String[] args) {LongAdder longAdder = new LongAdder();//0longAdder.increment();longAdder.increment();longAdder.increment();System.out.println(longAdder.longValue());// 3//初始化的时候传入自定义函数操作LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x * y,2);// 2longAccumulator.accumulate(1);//2longAccumulator.accumulate(2);//4longAccumulator.accumulate(3);//12System.out.println(longAccumulator.longValue());// 12}

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

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

相关文章

3.Labview字符串与路径精讲(下) — 字符串及路径的用法汇总

本章讲解labview中的字符串和路径具体实践用例&#xff0c;从前面板字符串属性到后面板字符串函数应用做出详细概述&#xff0c;通过本文的学习希望大家了解到字符串及路径在labview编程中的重要地位。 本系列文章为labview 从基础到强化到精通的学习文章&#xff0c;大家可以随…

奥比中光深度相机(二):PyQt5实现打开深度摄像头功能

文章目录 奥比中光深度相机&#xff08;二&#xff09;&#xff1a;PyQt5实现打开深度摄像头功能官方给出的调用深度相机源码环境精炼 UI界面设计逻辑代码构建槽函数连接提取视频流在界面中显示深度视频流注意关闭相机 总体代码效果演示运行main.py代码选择相机打开摄像头关闭摄…

HarmonyOS实战开发-如何实现一个简单的健康生活应用(上)

介绍 本篇Codelab介绍了如何实现一个简单的健康生活应用&#xff0c;主要功能包括&#xff1a; 用户可以创建最多6个健康生活任务&#xff08;早起&#xff0c;喝水&#xff0c;吃苹果&#xff0c;每日微笑&#xff0c;刷牙&#xff0c;早睡&#xff09;&#xff0c;并设置任…

BabySQL【2019极客大挑战】

知识点&#xff1a; 功能分析 登录界面一般是 where username and password 可以从username出手&#xff0c;注释掉and语句单引号闭合绕过 通过测试和报错信息发现是一个单引号读取输入可以单引号闭合绕过关键字过滤 or and 过滤 || &&替换双写绕过select from wher…

【leetcode】力扣简单题两数之和

题目 思路 代码实现 #include<iostream> #include<unordered_map>using namespace std;class Solution { public:vector<int> TwoNumber(const vector<int>& nums, int target){vector<int> number_vector;unordered_map<int, int> …

【Linux】常见命令

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 常用命令 1. ls2. pwd3. cd4. touch5. cat6. mkdir7. rm8. cp9. mv10. tail11. vim12.…

TCP粘包是怎么回事,如何处理?

还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#xff0c;webgl&#xff0c;ech…

基于《2023腾讯云容器和函数计算技术实践精选集》—探索腾讯云TKE的Docker容器、Serverless和微服务优势

重剑无锋&#xff0c;大巧不工。 ——金庸 腾讯云TKE&#xff0c;全称Tencent Kubernetes Engine&#xff0c;是一种完全托管式的容器服务。它可以帮助用户快速、高效地部署和管理Kubernetes集群&#xff0c;并提供一系列与之相关的云服务&#xff0c;如负载均衡、云硬盘、对象…

OSPF---开放式最短路径优先协议

1. OSPF描述 OSPF协议是一种链路状态协议。每个路由器负责发现、维护与邻居的关系&#xff0c;并将已知的邻居列表和链路费用LSU报文描述&#xff0c;通过可靠的泛洪与自治系统AS内的其他路由器周期性交互&#xff0c;学习到整个自治系统的网络拓扑结构;并通过自治系统边界的路…

掼蛋游戏规则

1、牌型&#xff1a;单牌、对牌、三张牌、三带二、顺子、同花顺、钢板&#xff08;例&#xff1a; 222333、444555&#xff09;、炸弹&#xff08;4涨以上相同的牌&#xff09;、三连对 2、牌大小&#xff1a;大王&#xff0c;小王&#xff0c;级牌&#xff0c;A&#xff0c;…

从学习海底捞到学习巴奴,中国餐饮带洋快餐重归“产品主义”

俗话说“民以食为天”&#xff0c;吃饭一向是国人的头等大事&#xff0c;餐饮业也是经济的强劲助推力。新世纪以来&#xff0c;餐饮业不断讲述着热辣滚烫的商业故事。 2006年&#xff0c;拥有“必胜客”、“肯德基”等品牌的餐饮巨头百胜集团&#xff0c;组织两百多名区域经理…

太阳能光伏发电应用场景有哪些?

随着全球能源结构的转型和环保意识的提升&#xff0c;太阳能光伏发电作为一种清洁、可再生的能源形式&#xff0c;其应用场景正日益广泛。下面&#xff0c;我们将详细探讨太阳能光伏发电的主要应用场景。 首先&#xff0c;工业领域是太阳能光伏发电的重要应用领域。工业厂房通常…

EasyCVR视频汇聚平台海康Ehome2.0与5.0设备接入时的配置区别

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

Nessus【部署 01】Linux环境部署漏洞扫描工具Nessus最新版详细过程分享(下载+安装+注册+激活)

Nessus最新版详细部署过程分享 1. 获取激活码2.主程序下载安装启动2.1 下载2.2安装2.3 启动 3.许可证及插件3.1 许可证获取3.2 插件安装 4.安装总结 Nessus官方网站&#xff1a; https://www.tenable.com/products/nessus/nessus-essentials 及介绍&#xff1a; 国际数据公司&…

编程语言 MoonBit 本周有超多重磅更新等你来探索:expect 测试添加 inspect 函数,还有……

MoonBit 更新 1. expect 测试添加 inspect 函数 expect 测试添加针对 Show 接口的 inspect 函数&#xff0c;签名如下&#xff1a; pub fn inspect(obj: Show,~content: String "",~loc: SourceLoc _,~args_loc: ArgsLoc _ ) -> Result[Unit, String]⚠️ 此…

C++函数重载引用

函数重载 自然语言中&#xff0c;一个词可以有多重含义&#xff0c;人们可以通过上下文来判断该词真实的含义&#xff0c;即该词被重载了。比如&#xff1a;以前有一个笑话&#xff0c;国有两个体育项目大家根本不用看&#xff0c;也不用担心。一个是乒乓球&#xff0c;一个是男…

Mybatis(3) web项目

web项目 1、准备2、分析3、 MyBatis对象作用域以及事务问题4、问题 实现一个转账系统 1、准备 ①准备一个web模块 在这里使用了maven archetype&#xff0c;选择web 之后会生成 一个web模块&#xff0c;但是不同的版本可能不同&#xff0c;在这里我就没有java和resources目录&…

KUKA机器人更改时间和HMI最小化设置

在使用 KUKA 机器人时&#xff0c;示教器上左边有个“表”的图标&#xff0c;点一下就会显示时间。但一般不准&#xff0c;想要更改时间可以通过HMI最小化后进行更改设置。更改时间需要将示教器界面最小化&#xff0c;也就是进入Windows 界面。通过以下步骤可以进行设置&#x…

ThreadLocal的基本使用

一、ThreadLocal的介绍 ThreadLocal 是 Java 中的一个类&#xff0c;它提供了线程局部变量的功能。线程局部变量是指每个线程拥有自己独立的变量副本&#xff0c;这些变量在不同的线程中互不影响。ThreadLocal 提供了一种在多线程环境下&#xff0c;每个线程都可以独立访问自己…

多叉树题目:N 叉树的最大深度

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;N 叉树的最大深度 出处&#xff1a;559. N 叉树的最大深度 难度 3 级 题目描述 要求 给定一个 N 叉树&#xf…