当前位置: 首页 > web >正文

CAS(Compare And Swap)

1. 乐观锁 - 悲观锁

悲观锁:以大家比较了解的sychronized为例子,其就是一个比较典型的悲观锁,悲观锁因为比较悲观,它认为在任何情况下都会天有不测风云,也就是说线程每次访问共享资源的时候都会出现冲突,所以必须先对数据进行上锁,从而保证对应的临界区资源在同一个时间段只能有一个资源去访问。

乐观锁:乐天派,它认为每次访问都不会出现对共享资源访问的冲突,所以在对共享资源访问的时候不会上锁,即多个线程均无需加锁,也无需等待。如果出现对共享资源访问的冲突,那么就会使用CAS的技术以确保线程执行的安全性。

从乐观锁的角度来看,其是一定不会出现死锁的现象的,因为其不上锁,所有线程都不会阻塞等待,永远的乐天派;悲观锁就相反了,逻辑处理不好就很容易出现死锁的现象。

悲观锁:多用于“写多读少”,以避免共享资源修改冲突

乐观锁:多用于“读多写少”,以提高效率。

以此引入CAS的技术,下面让我们来解开CAS神秘的面纱。

2. 什么是CAS?

        2.1 简要概念

        CAS(Compare And Swap)是一种硬件级别的原子操作,通过比较当前值(Var)跟旧值也可以叫做预期值(Expect)是否相等,如果相等,那么就将当前值修改为新值(New);反之则代表对应的资源被其他线程修改过,则放弃此次修改。但是这里的“放弃”并不是直接挂起,而是允许其再次进行尝试,尝试的过程依旧会占用CPU资源,自然,尝试也有一定的次数限制。这其实就是自旋,即:

        自旋是一种线程在竞争共享资源时不立即阻塞,而是通过循环(忙等待)反复尝试获取锁或执行操作 的机制。其核心目的是 避免线程挂起和唤醒的开销(如上下文切换、内核态切换),适用于 短时间等待 的场景。

        a. CAS三要素

  • 当前值V(var)
  • 旧值 || 预期值(Expect)
  • 新值(New)

在上面已经大致说过了其运行的基本原理,就是通过比较当前值跟旧值是否相等,相等代表未被修改,将当前值V变为新值N,不等则不做修改,不断尝试,再次比较。

举一个例子:

        现在在多线程的状态下,多个线程同时想要修改一个变量 S(代指Var) ,其原本的值为 S = 5(代指Expect),需要对其进行+1的操作,变成新值 6(代指New),其执行的流程如下:

        首先,线程会比较当前值 S 跟旧值 5 是否一样,确保没有被修改,如果一样,将其更换为新值 6,这个时候 S的值就变成了6

        如果发现S != 5 那么当前线程就会放弃本次修改操作,之后再尝试数次,次数用尽后自动放弃修改。

        那么会不会出现说,我现在的S = 5,也就是跟旧值是一样的,但是准备修改的时候出现其他的线程将其修改为新值6了呢?不用担心,这种情况不会发生,因为CAS在底层是具有原子性的,要么同时成功,要么不成功。它是一种系统原语,是一条CPU指令,所以说底层是具有原子性的。

        2,2  CAS优缺点

        优点:

  • 无锁竞争,所以其并不会导致线程为了争抢共享资源而导致的线程阻塞,大大提高了线程的并发能力
  • 原子性,CAS所执行的每一条指令因为对应的都是底层的,有关操作系统的原语,所以具有较好的原子性,保证线程安全。

        缺点:

  • 自旋开销:自旋所造成的CPU资源的浪费,尤其是在高并发的场景下
  • ABA问题:如果有线程将一个值,从A修改为B,之后再从B修改为A,那么在其他的线程看来,是没有发生任何修改的,也就是V跟E还是一样的,那么就会让多个线程均修改成功。
  • 单变量限制:CAS操作仅仅使用于对于单个变量的更新操作,不适用于涉及多个变量的复杂操作

        2.3 CAS所面对的三个问题

        a.ABA问题:

        解决ABA问题,比较常见的方法就是使用版本号或者是时间戳用以进行记录,每次修改不仅修改对应的值,而且记录当下的时间戳,这样就保证了每次的原子性修改一定是跟之前的不一样的,从而解决ABA问题。

而在AtomicStampedReference当中,就设置了一个Pair内部类,用于记录时间戳,同时还使用native作了一个标记,使得这个静态变量具有可见性,如图:

        b. 长时间自旋问题:

        首先必须理清楚一个概念,为什么CAS看起来这样好,但是却只推荐在并发量比较小的时候使用?类似于sychnoized当中的轻量级锁,在高并发的场景下其依旧无法替代像mutex这样的基于互斥锁实现的功能?一个方面就是因为CAS的操作其实大部分都是自旋的,高并发情况下,可能有的线程它需要一段时间才能获取到对应的资源,那么在这一段时间内,其并没有阻塞,而是一直在运行,吃CPU资源,线程比较少还好,一旦躲起来,CPU负担可想而知。

        也就是说,线程在自旋失败之后,还是在不简短的发起读请求,从而对CPU造成了比较大的资源浪费。针对于此,解决思路是让JVM支持硬件级处理器提供的pause指令。

        pause指令能够让自旋失败的线程,先睡眠一段时间,之后再继续自旋。需要注意的一点是,在pause的这段时间里,此线程还是占有CPU的,不是像线程阻塞一样,直接放弃。睡眠的这一段时间,能够使得读操作的频率降低很多,提高效率。

        c. 单变量限制

        CAS对于单个变量能够保持原子性,但是共享变量一旦变成多个,那么就无法保证了,此时解决方法有两个

  • 使用AtomicReference保证对象之间具有原子性,把多个变量放到以恶个对象里面进行CAS操作,从而保证这些共享变量的原子性
  • 添加锁。锁内部的代码,只有当前的线程能够进行操作,其实跟上面那种方法类似,都是将这些共享变量看作是一个整体去操作了,从而确保原子性。

3. CAS在JAVA中的实现

        在JAVA当中,CAS操作是由一个内部类Unsafe实现的,但是这个类是内部的,所以并不推荐直接使用。更具体的是通过JNI(Java Native Interface)实现的,如图所示:

        通过这些操作实现CAS,确保其原子性。

        在Java当中,可以使用并发包Atomic这些类,他们封装了CAS操作,提供了线程安全的原子操作。

http://www.xdnf.cn/news/1588.html

相关文章:

  • Ubuntu服务器上如何监控Oracle数据库
  • 电子削铅笔刀顺序图详解:从UML设计到PlantUML实现
  • 几种查看PyTorch、cuda 和 Python 版本方法
  • 关于Qt对Html/CSS的支持
  • 全链路数据仓建设指南:从构建流程到应用场景
  • Vue+Flask豆瓣LSTM影评+推荐算法大数据可视化平台深度学习系统源码
  • 文件上传--WAF绕过干货
  • 【网络入侵检测】基于Suricata源码分析NFQ IPS模式实现
  • Python torchvision.transforms 下常用图像处理方法
  • maven工程中引入外部jar
  • 数据分析之技术干货业务价值​​ powerquery 分组排序后取TOP
  • 《AI大模型应知应会100篇》 第36篇:RAG技术入门:检索增强生成原理及实现
  • 【hadoop】HBase分布式数据库安装部署
  • PyTorch生成式人工智能实战(2)——PyTorch基础
  • 13、性能优化:魔法的流畅之道——React 19 memo/lazy
  • Websocket自动发送消息客户端工具
  • LeetCode每日一题4.24
  • 硬核解析!电动汽车能耗预测与续驶里程的关键技术研究
  • 多模态大模型 Qwen2.5-VL 的学习之旅
  • 立錡科技优化 HDD、LPDDR、SoC 供电的高性能降压转换器
  • 6 种AI实用的方法,快速修复模糊照片
  • 负环-P3385-P2136
  • 让Docker端口映射受Firewall管理而非iptables
  • LVGL在VScode的WSL2中仿真
  • R 语言科研绘图第 41 期 --- 桑基图-基础
  • .NET Framework 4.0可用EXCEL导入至DataTable
  • centos7的环境下ollama 如何卸载
  • 【Linux网络】应用层自定义协议与序列化及Socket模拟封装
  • 第十五届蓝桥杯 2024 C/C++组 拼正方形
  • 深度对比评测:n8n vs Coze(扣子) vs Dify - 自动化工作流工具全解析