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

深入理解 CAS:并发编程的原子操作基石

前言:

        在多线程并发编程中,保证数据操作的原子性是一个关键问题。传统的锁机制(如synchronized)虽然能够保证原子性,但会带来性能开销和线程阻塞等问题。为了解决这些问题,CAS作为一种乐观锁技术应运而生,凭借其无锁、高效的特性,成为了并发编程领域的重要基石。

一、什么是CAS?

        CAS的全称是Compare AndSwap,中文意思是“比较并交换”。CAS 是一种乐观锁技术,它基于硬件原语(如 CPU 的cmpxchg指令)实现,用于在多线程环境下对共享变量进行原子操作。

CAS 操作包含三个核心参数

  • 内存地址 V:要操作的共享变量在内存中的地址。
  • 预期值 A:线程执行 CAS 操作前,认为该共享变量应有的值。
  • 新值 B:如果共享变量的当前值与预期值 A 一致,线程希望将其更新为的新值。

CAS 的核心逻辑是:比较内存地址 V 中的值与预期值 A 是否相等,如果相等,就将该值更新为新值 B;如果不相等,说明有其他线程修改过该变量,当前线程不进行更新,但会获取变量的最新值,然后重新尝试 CAS 操作(通常是循环重试,即 “自旋”)。整个过程是原子的,不会被其他线程中断。

二、CAS执行流程

CAS操作的执行流程可以概括为”读取-计算-比较并交换“,具体步骤如下:

  1. 读取内存位置的当前值作为预期原值A

  2. 计算新值B

  3. 执行CAS操作,比较内存位置当前值是否仍等于A

        a.如果相等,说明变量值未被其他线程修改,执行交换操作,将内存值更新为B;

        b.如果不相等,说明变量值已被其他线程修改,操作失败。

4.重试机制:可以通过Java代码中的while循环再次调用cmpxchg指令进行重试,直到设置成功为止。这种"读取-计算-比较并交换"的循环模式就是著名的"自旋"。

三、Unsafe 类

        在 Java 中,CAS 机制是通过 sun.misc.Unsafe 类提供的 compareAndSwapXXX() 等 CAS 方法来实现的。sun.misc.Unsafe 是 JDK 提供的一个底层工具类。它提供内存操作、CAS、对象操作等 “不安全” 的功能,让 JDK 能够使用 Java 代码来实现原本需要使用 native 本地方法(C 或 C++)才可以实现的功能。由于该类不应该在 JDK 核心类库之外使用,所以被命名为 Unsafe(不安全)。

        Unsafe类提供了 objectFieldOffset() 方法,用于获取某个字段相对 Java 对象的 “起始地址” 的偏移量(内存位置),还有 getInt()getLong()getObject() 等方法。可以按照字段偏移量,直接访问内存中 Java 对象的某个字段的内存地址。

四、Unsafe 实现 CAS 的工作原理

AtomicInteger类:

Unsafe类:

        首先读取当前对象obj在主内存中的值,并保存到value中,然后通过循环,判断当前对象在主内存中的值是否等于value,如果相同,就自增(交换value与value+val两个值),否则继续循环,重新获取value值。
在上述逻辑中核心方法是compareAndSwapInt()方法,它是一个native本地方法,通过JNI(Java Native Interface)调用底层C++代码,执行cPU指令是cmpxchg。
在getAndAddInt()方法中通过do...while循环操作实现自旋锁:当预期值和主内存中的值不等时,就重新获取主内存中的值。

五、基于 CAS 的 AtomicInteger

 AtomicInteger是JUC包中基于CAS实现的原子整数类,它提供了一系列原子操作的方法。

public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID = 6214790243416807050L;// 获取Unsafe实例private static final Unsafe unsafe = Unsafe.getUnsafe();// 存储value字段的内存偏移地址private static final long valueOffset;static {try {// 获取value字段的偏移地址valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}// 当前值,使用volatile保证可见性private volatile int value;// CAS更新操作public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}// 原子递增public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}// 其他原子方法...
}

六、 CAS的缺点

1.循环时间长开销大

        在Unsafe的实现中使用了自旋锁的机制。在该环节如果CAS操作失败,就需要循环进行CAS操作(do...while循环同时读取最新的期望值),如果长时间都不成功的话,那么会造成CPU极大的开销。

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

        CAS操作只能保证一个共享变量的原子性,但如果存在多个共享变量,或一整个代码块的逻辑需要保证线程安全,CAS就无法保证原子性操作了。此时,就需要考虑采用加锁方式(悲观锁)保证原子性。

3.ABA问题

        ABA问题是CAS机制中的一个经典问题:如果一个变量的值从A变为B,然后又变回A,那么CAS操作会误认为它没有被修改过。

例如:

  1. 线程1读取变量值为A

  2. 线程2将值从A改为B

  3. 线程3又将值从B改回A

  4. 线程1执行CAS操作,发现值仍是A,认为没有被修改过,操作成功

虽然大多数情况下这不是问题,但在某些场景下(如链表操作)可能导致错误。

解决方案:可以通过JDK的Atomic包中的AtomicStampedReference类来解决,使用compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

七、结语

        CAS作为一种高效的无锁并发机制,在Java并发编程中发挥着重要作用。它通过硬件级别的原子指令实现了线程安全,避免了传统锁机制的性能开销。然而,CAS也存在一些局限性,需要根据具体场景选择合适的使用方式。理解CAS的原理和实现对于编写高性能并发程序至关重要。


文章转载自:

http://cnsmPHPn.rmtxp.cn
http://jPZjbixK.rmtxp.cn
http://wA40DfIf.rmtxp.cn
http://NQttUvZ4.rmtxp.cn
http://Ysl5go6E.rmtxp.cn
http://hkQ2kFvA.rmtxp.cn
http://mweaqUki.rmtxp.cn
http://g15yflLT.rmtxp.cn
http://wRsNZliM.rmtxp.cn
http://GD9huRuB.rmtxp.cn
http://9Ko5Wpg5.rmtxp.cn
http://xzZkcHqf.rmtxp.cn
http://yp5Sp1Ia.rmtxp.cn
http://U0SKoTa2.rmtxp.cn
http://g18WUi85.rmtxp.cn
http://rR4RJBSf.rmtxp.cn
http://QYgmAmAw.rmtxp.cn
http://I09wXfqe.rmtxp.cn
http://mxHeIYiE.rmtxp.cn
http://D8Xmck2v.rmtxp.cn
http://4PCc4frt.rmtxp.cn
http://N4OvE0vB.rmtxp.cn
http://GA2RlwSh.rmtxp.cn
http://EITldQUD.rmtxp.cn
http://W6JEApUa.rmtxp.cn
http://q9CMr6Ez.rmtxp.cn
http://QDLIdlwZ.rmtxp.cn
http://DO1siLjZ.rmtxp.cn
http://0xJdCyyF.rmtxp.cn
http://SggA2GIF.rmtxp.cn
http://www.dtcms.com/a/385193.html

相关文章:

  • 矿用本安三电车变频器绝缘监测
  • 如何录制带解说的教学视频?屏幕录制工具推荐ASCOMP Screencapt Pro
  • 多模态视频理解领域 Benchmark 与 Leaderboard 整理
  • 《投资-54》元宇宙
  • OpenLayers数据源集成 -- 章节十四:WKT图层详解:标准几何文本格式的精确解析与渲染方案
  • U8g2 库驱动oled
  • 【NTC热敏电阻】NTC电阻测温电路与ADC换算
  • Gradle深度解析:从构建工具到开发生态系统
  • 本地搭建redis-cluster开发环境
  • 优化浏览体验:4个设置让Google Chrome更好用!
  • V100 部署qwen2.5-vl
  • 企业能源管控联网管理解决方案:为企业节能增效
  • [Dify] 实现“多知识库切换”功能的最佳实践
  • AI大模型开发(多模态+提示词)
  • 专项智能练习(行为主义学习理论)
  • Java 大视界 -- Java 大数据实战:618 精准发券核销率 15%→42%(含生产级代码 + 避坑指南)
  • HarmonyOS 5.0应用开发——V2装饰器@local的使用
  • Redis数据结构:ZipList与Listpack
  • 数据库选型指南:从需求分析到技术决策的全方位解析
  • Linex操作系统-Shell脚本(四)
  • 浏览器为啥要对 JavaScript 定时器“踩刹车”?
  • Linux网络:socket编程TCP
  • 基于python大数据的游戏数据分析系统的设计与实现
  • 巧用ebpf排查linux网络问题
  • Android音频学习(十八)——混音流程
  • Android 开发布局问题:android:layout_weight 属性不生效
  • Android WorkManager的概念和使用
  • PyTorch实战——基于LSTM的情感分析模型
  • 深入浅出 MySQL 的 MVCC:多版本并发控制的工作机制与应用
  • 【完整源码+数据集+部署教程】衬衫组件图像分割系统: yolov8-seg-C2f-EMBC