CAS的使用以及底层原理详解

什么是 CAS ?

CAS 全称 Compare And Swap,翻译为中文是比较并交换,是一种无锁的原子操作,CAS 可以不使用锁来保证多线程修改数据的安全性,虽说是无锁但实际上使用了一种乐观锁的思想,也可以认为 CAS 是乐观锁的一种实现。

CAS 的原理?

CAS 原理是依赖于硬件支持的原子性指令来实现的, CAS 在 Java 中的实现是由 Unsafe 类来实现的,Unsafe 类是 JDK 提供的一个不安全的类,它提供了一些底层的操作,它的作用是让 Java 可以在底层直接操作内存,从而提高程序的效率,Unsafe 类中就提供了关于 CAS 的方法。

CAS 的作用?

CAS 可以实现无锁情况的下的原子操作,当多线程在修改同一个共享变量的时候,只要一个线程可以更新成功,其他线程会更新失败,失败的线程不会被挂起(这里就节省了线程挂起和唤醒的开销),还可以继续尝试修改,使用 CAS 就实现了 synchronized、ReentrantLock 等锁的同样效果,但开销会更小,因此,CAS操作广泛应用于并发编程中,例如我们熟知的 AbstractQueuedSynchronizer、ReentrantLock 等都广泛使用了 CAS 。

CAS 的用法?

CAS 操作主要由三个参数,分别是要更新的参数、期望值、新值,CAS 操作会先获取共享变量在内存中的值与期望值进行比较,如果相同,则把变量设置为新值,否则就修改失败,可以重新进行 CAS 操作。

CAS 的优缺点?

  • CAS 依赖于硬件层面的原子指令,实现无锁并发,省去了线程加锁解锁的开销。
  • CAS 会出现 ABA 问题。
  • CAS 自旋如果不成功,会给 CPU 带来较大的开销,因此 CAS 不适合使用在竞争很大的场景。
  • CAS 只能保证单个变量操作的原子性,有一定使用局限性,而如 synchronized、ReentrantLock 等锁就没有这个问题。

什么是 CAS 的 ABA?

ABA 问题简单来说就是一个变量初始值为 A,被修改成 B,然后又被修改成 A,CAS 是无法识别到这个过程的。

ABA 流程演示:

在这里插入图片描述
分析 ABA 流程:

  • 线程1 读取到共享变量 str 的初始值为 A,准备执行 CAS(A,B) 操作。
  • 此时线程2 抢占了 CPU 时间片,读取到共享变量 str 的初始只为 A,执行 CAS(A,B) 操作。
  • 接着线程2 继续执行 CAS(B,A) 操作,此时共享变量 str 的值为 A,线程2 释放了 CPU 时间片。
  • 线程1 终于抢回了 CPU 时间片,继续执行 CAS(A,B) 操作,执行成功结束。

整个流程有个很明显的问题,享变量 str 被线程2 修改为 B,然后再次修改回 A,而线程1 没有察觉到,还是正常的执行了 CAS 操作。

怎么解决 ABA 问题?

ABA问题最简单的解决方案就是使用版本号,在每次修改数据时都携带一个版本号,只有当该版本号与数据的版本号一致时,才能执行数据的修改,否则修改失败,因为 CAS 操作时携带了版本号,而版本号在每次修改时都会递增,并且只会增加不会减少,不会出现版本号一直的问题,也就有效的避免了 ABA 问题。

Java 中如何解决 ABA 问题?

Java 中使用了时间错来解决 CAS ABA 问题,其实也是版本号的思想,Java 提供了AtomicStampedReference 和 AtomicMarkableReference 来解决 ABA 问题,这两个类在 CAS 的基础上加了一个时间戳,也就是版本号,时间戳是递增的,,,。

AtomicStampedReference 类 weakCompareAndSet 方法源码分析:

public boolean weakCompareAndSet(V   expectedReference,V   newReference,int expectedStamp,int newStamp) {return compareAndSet(expectedReference, newReference,expectedStamp, newStamp);}//expectedReference 期望的值//newReference 更新的值//expectedStamp 期望的时间戳//newStamp 更新后的时间戳public boolean compareAndSet(V   expectedReference,V   newReference,int expectedStamp,int newStamp) {Pair<V> current = pair;//只有当前值等于期望值 且当前时间戳等于期望时间戳 才会执行 CAS 操作returnexpectedReference == current.reference &&expectedStamp == current.stamp &&((newReference == current.reference &&newStamp == current.stamp) ||casPair(current, Pair.of(newReference, newStamp)));}

根据源码分析可知,AtomicStampedReference 类 weakCompareAndSet 方法只关心期望值和当前值是否相同,并不会关注是否被修改过,而 AtomicMarkableRederence 类的 weakCompareAndSet 方法会关注是否被修改过,下面我来分析一下。

AtomicMarkableRederence 类的 weakCompareAndSet 方法源码分析:

 public boolean weakCompareAndSet(V expectedReference,V newReference,boolean expectedMark,boolean newMark) {return compareAndSet(expectedReference, newReference,expectedMark, newMark);}//expectedReference 期望的值
//newReference 更新的值
//expectedStamp 期望的标识
//newStamp 更新后的标识 
public boolean compareAndSet(V expectedReference,V newReference,boolean expectedMark,boolean newMark) {Pair<V> current = pair;returnexpectedReference == current.reference &&expectedMark == current.mark &&((newReference == current.reference &&newMark == current.mark) ||casPair(current, Pair.of(newReference, newMark)));}

根据源码分析可知, AtomicMarkableRederence 类的 weakCompareAndSet 方法没有时间戳入参,只有 boolean 类型的修改表标识入参,用来标记变量是否被修改过。

CAS 应用场景:

  • 数据库并发控制,乐观锁就是通过 CAS 思想来实现的,它可以在数据库并发控制中保证多个事务同时访问同一数据时的一致性。
  • 自旋锁,自旋锁是一种非阻塞锁,当线程尝试获取锁时,如果锁已经被其他线程占用,则线程不会进入休眠,而是一直在自旋等待锁的释放,AbstractQueuedSynchronizer 中就有大量使用。
  • 线程安全计数器,由于CAS操作是原子性的,可以使用 CAS 设计实现一个线程安全的计数器;。
  • 队列,在并发编程中,队列经常用于多线程之间的数据交换,AbstractQueuedSynchronizer 的 CHL 队列中节点状态变更、节点的变更就大量使用了 CAS 操作。

CAS 在并发编程中使用广泛,值得我们深入研究,有利用我们更好的理解一些底层框架源码。

如有错误的地方欢迎指出纠正。

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

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

相关文章

【Go】十七、进程、线程、协程

文章目录 1、进程、线程2、协程3、主死从随4、启动多个协程5、使用WaitGroup控制协程退出6、多协程操作同一个数据7、互斥锁8、读写锁9、deferrecover优化多协程 1、进程、线程 进程作为资源分配的单位&#xff0c;在内存中会为每个进程分配不同的内存区域 一个进程下面有多个…

QT-自定义参数设计框架软件

QT-自定义参数设计框架软件 前言一、演示效果二、使用步骤1.应用进行参数注册2.数据库操作单例对象3.参数操作单例对象 三、下载链接 前言 常用本地数据参数通常使用的是xml等文本的格式&#xff0c;进行本地的数据参数的存储。这种参数的保存方式有个致命的一点&#xff0c;就…

基于深度学习的机场航拍小目标检测系统(网页版+YOLOv8/v7/v6/v5代码+训练数据集)

摘要&#xff1a;在本博客中介绍了基于YOLOv8/v7/v6/v5的机场航拍小目标检测系统。该系统的核心技术是采用YOLOv8&#xff0c;并整合了YOLOv7、YOLOv6、YOLOv5算法&#xff0c;从而进行性能指标的综合对比。我们详细介绍了国内外在机场航拍小目标检测领域的研究现状、数据集处理…

STM32 直接修改寄存器来输出内部时钟的方法

1. 在特殊情况下使能 MCO 功能的方法 在对某些不容易复现的问题进行代码调时&#xff0c;需要观察内部时钟的情况&#xff0c;但往往代码之前并没有使能 MCO 功能&#xff0c;在这种情况下就可以使用寄存器直接配置来输出内部时钟到GPIO 脚位上进行观察和测试。 下面的例子就…

mongodb的简单操作

文章目录 前言数据库的创建和删除集合的创建和删除文档的插入和查询异常处理更新数据局部修改符合条件的批量更新加操作 删除文档删除全部数据删除符合条件的数据 统计count统计有多少条数据统计特定条件有多少条数据 分页查询排序查询正则查询比较查询包含查询条件连接查询索引…

Ollama教程——入门:开启本地大型语言模型开发之旅

Ollama教程——入门&#xff1a;开启本地大型语言模型开发之旅 引言安装ollamamacOSWindows预览版LinuxDocker ollama的库和工具ollama-pythonollama-js 快速开始运行模型访问模型库 自定义模型从GGUF导入模型自定义提示 CLI参考创建模型拉取模型删除模型复制模型多行输入多模态…

Springboot整合Milvus向量库

1. Milvus的Maven依赖&#xff0c; 配置如下 <dependency><groupId>io.milvus</groupId><artifactId>milvus-sdk-java</artifactId><version>2.3.4</version><exclusions><exclusion><artifactId>log4j-slf4j-imp…

Redis的5大常见数据类型的用法

上一篇文章我们讲了Redis的10大应用场景&#xff0c;这一篇文章就针对Redis的常用数据结构进行一个说明&#xff0c;通过示例的形式演示每一种数据结构如何使用。 当涉及Redis的数据操作时&#xff0c;不同数据类型对应的不同数据结构&#xff0c;如下就对5大常用的数据类型进行…

三台电机的顺启逆停

1&#xff0c;开启按钮输入信号是 电机一开始启动&#xff0c;5秒回电机2启动 &#xff0c;在5秒电机三启动 关闭按钮输入时电机3关闭 &#xff0c;5秒后电机2关闭 最后电机一关闭 2&#xff0c;思路开启按钮按下接通电机1 并且接通定时器T0 定时器T0 到时候接通电机2 并且开…

【漏洞复现】WordPress Plugin LearnDash LMS 敏感信息暴漏

漏洞描述 WordPress和WordPress plugin都是WordPress基金会的产品。WordPress是一套使用PHP语言开发的博客平台。该平台支持在PHP和MySQL的服务器上架设个人博客网站。WordPress plugin是一个应用插件。 WordPress Plugin LearnDash LMS 4.10.2及之前版本存在安全漏洞&#x…

vCenter Server出现no healthy upstream的解决方法

https://blog.51cto.com/wangchunhai/4907250 访问vCenter 7.0 地址后&#xff0c;页面出现“no healthy upstream”,无法正常登录vCenter&#xff0c;重启后依旧如此&#xff0c;该故障的前提是没有对vCenter做过任何配置&#xff0c;如下图所示。 尝试登录"VMware vCen…

天龙八部_暗黑机制_人面桃花_单机架设搭建

一. 搭建成功视频演示 天龙八部_暗黑机制_人面桃花_单机架设搭建 二. 一些文件截图 完整教程和搭建文件获取: https://githubs.xyz/y24.html 三. 搭建步骤 安装虚拟机虚拟机打开一键端&#xff0c;然后登录root&#xff0c;密码&#xff1a;123456启动./run 脚本 &#xff0…