【JUC进阶】一文深度讲解CAS
创始人
2025-05-31 22:31:29
0

文章目录

  • 1. 什么是CAS
  • 2. CAS的工作原理
  • 3. CAS的缺点
    • 3.1 循环时间长开销大
    • 3.2 只能保证一个共享变量的原子操作
    • 3.3 ABA问题

1. 什么是CAS

在JDK5之前,可以通过synchronized或Lock来保证高并发的业务场景下的线程安全,但是synchronized或Lock都属于互斥锁的方案,互斥锁所带来的比较重量级、加锁、释放锁都会带来性能上的损耗。

于是,就出现了CAS机制实现无锁的解决方案,CAS和乐观锁类似,CAS可以达到非阻塞同步的方式来保证线程安全。

CAS是现代CPU广泛支持的一种对内存中共享数据进行操作的一种特殊指令这个指令可以对内存中的共享数据做原子的读写操作。

CAS的核心思想就是让CPU比较内存中某个值是否和预期值相同,如果相同则将这个值设置为新值,如果不相同则不做更新操作。

image-20211231142156322

CAS的基本流程如上所示:

  1. 程序一开始读取某块内存的值为E,现要将数据E更新为数据V
  2. 将新的数据V写入这块内存之前,首先比较一下当前这个内存的值是否等于E
    1. 如果等于,那么说明没有其他线程修改过这个内存的值(这样说法是错的,因为还存在ABA问题),进行更新操作。
    2. 如果不等于,说明其他线程将这个内存的值修改了,则不进行更新操作。

上述这些操作都是由CPU指令来保证原子性的。


2. CAS的工作原理

在CAS底层的实现原理,实际上是通过Unsafe类和自旋锁来实完成的。

AtomicInteger源码中,可以看见都是通过Unsafe类来实现更新的。

Unsafe类是JDK内部常用工具栏,**它通过暴露一些Java意义上说“不安全”的功能给Java层代码,来让JDK能够更多的使用Java代码来实现一些原本是平台相关的、需要使用native语言(例如C或C++)才可以实现的功能。**该类不应该在JDK核心类库之外使用,这也是命名为Unsafe(不安全)的原因。

public class AtomicInteger extends Number implements java.io.Serializable {private static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static {try {// 用于获取value字段相对当前对象的“起始地址”的偏移量valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}private volatile int value;//返回当前值public final int get() {return value;}//递增加detlapublic final int getAndAdd(int delta) {// 1、this:当前的实例 // 2、valueOffset:value实例变量的偏移量 // 3、delta:当前value要加上的数(value+delta)。return unsafe.getAndAddInt(this, valueOffset, delta);}//递增加1public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}...
}

Unsafe里关于对象字段访问的方法把对象布局抽象出来,它提供了objectFieldOffset()方法用于获取某个字段相对Java对象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java对象的某个字段。在AtomicIntegerstatic代码块中便使用了objectFieldOffset()方法。

Unsafe类的功能主要分为内存操作、CAS、Class相关、对象操作、数组相关、内存屏障、系统相关、线程调度等功能。这里我们只需要知道其功能即可,方便理解CAS的实现,注意不建议在日常开发中使用。

public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

上述代码等于是AtomicInteger调用UnSafe类的CAS方法,JVM帮我们实现出汇编指令,从而实现原子操作。

public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;
}

在上面的getAndAddInt方法中三个参数

  1. 第一个参数表示当前对象,也就是new的那个AtomicInteger对象
  2. 第二个表示内存地址;
  3. 第三个表示自增步伐,在AtomicInteger#incrementAndGet中默认的自增步伐是1。

getAndAddInt方法中,会首先将当前对象主内存的值赋值给val5,然后进入while循环,判断当前对象此刻主内存中的值是否等于val5,如果是,那么就更新内存中的值,否则继续循环,重写获取val5的值(这里是针对上面的incrementAndGet自增来说的)

这样的话即使有其他线程修改了内存的值,CAS会比对内存值是否和预期值相同从而判断是否要更新内存的值。同时因为上面的compareAndSwapInt是一个native方法,这个方法汇编之后是CPU原语指令,原语指令是连续执行不会被打断的,所以可以保证原子性。

像上面getAndAddInt方法中的do while循环操作,就是所谓的自旋,如果预期值和主内存的值不一样,则需要重新获取主内存的值,这就是自旋。

CAS虽然解决了多线程下的安全问题,但是存在一个非常明显的缺点,那就是当内存使用自旋进行CAS更新的时候(while循环CAS更细你,如果更新失败,则),如果长时间不成功,对CPU来说将会造成极大的开销。


3. CAS的缺点

CAS虽然高效实现了原子性的操作,但是存在下面三个缺点:

  1. 循环时间长,开销大
  2. 只能保证一个共享变量的原子操作
  3. ABA问题

3.1 循环时间长开销大

前面也提到了Unsafe的实现使用了自旋锁的机制,如果当前CAS操作失败,就需要循环进行CAS,如果长时间不成功,那么就会造成CPU极大的开销。


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

CAS是一个针对一个共享变量使用的机制,可以保证原子性,但是如果存在多个共享变量,或者需要一整块代码的逻辑都保证线程安全,那么CAS就无法保证原子操作了,这时候就需要考虑使用synchronized或Lock这些重量级锁来保证线程安全了。

或者将多个共享变量合并程一个共享变量从而进行CAS操作。


3.3 ABA问题

虽然使用CAS可以实现非阻塞式的原子性操作,但是会产生ABA问题

  1. 线程A在共享变量读到的值为A
  2. 线程A被抢夺了,线程B执行
  3. 线程B将数据A改为B,再改为A,此时被线程A抢占
  4. 线程A此时看见共享变量的值没有改变,于是继续执行

虽然线程A以为变量值没有改变,继续执行了,但是这个会引发一些潜在的问题。ABA问题最容易发生在lock free的算法中的,CAS首当其冲,因为CAS判断的是指针的地址。如果这个地址被重用了呢,问题就很大了(地址被重用是很经常发生的,一个内存分配后释放了,再分配,很有可能还是原来的地址)。

ABA问题的解决思路就是使用版本号:在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A->B->A就会变成1A->2B->3A。

从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。


参考:一篇搞定CAS,深度讲解,面试实践必备 - 腾讯云开发者社区-腾讯云 (tencent.com)

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
JAVA多线程知识整理 Java多线程基础 线程的创建和启动 继承Thread类来创建并启动 自定义Thread类的子类&#...
【洛谷 P1090】[NOIP... [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G ...
国民技术LPUART介绍 低功耗通用异步接收器(LPUART) 简介 低功耗通用异步收发器...
城乡供水一体化平台-助力乡村振... 城乡供水一体化管理系统建设方案 城乡供水一体化管理系统是运用云计算、大数据等信息化手段࿰...
程序的循环结构和random库...   第三个参数就是步长     引入文件时记得指明字符格式,否则读入不了 ...
中国版ChatGPT在哪些方面... 目录 一、中国巨大的市场需求 二、中国企业加速创新 三、中国的人工智能发展 四、企业愿景的推进 五、...
报名开启 | 共赴一场 Flu... 2023 年 1 月 25 日,Flutter Forward 大会在肯尼亚首都内罗毕...
汇编00-MASM 和 Vis... Qt源码解析 索引 汇编逆向--- MASM 和 Visual Studio入门 前提知识ÿ...
【简陋Web应用3】实现人脸比... 文章目录🍉 前情提要🌷 效果演示🥝 实现过程1. u...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
windows安装JDK步骤 一、 下载JDK安装包 下载地址:https://www.oracle.com/jav...
分治法实现合并排序(归并排序)... 🎊【数据结构与算法】专题正在持续更新中,各种数据结构的创建原理与运用✨...
在linux上安装配置node... 目录前言1,关于nodejs2,配置环境变量3,总结 前言...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
Linux内核进程管理并发同步... 并发同步并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理...
opencv学习-HOG LO... 目录1. HOG(Histogram of Oriented Gradients,方向梯度直方图)1...
EEG微状态的功能意义 导读大脑的瞬时全局功能状态反映在其电场结构上。聚类分析方法一致地提取了四种头表面脑电场结构ÿ...
【Unity 手写PBR】Bu... 写在前面 前期积累: GAMES101作业7提高-实现微表面模型你需要了解的知识 【技...