深入理解CAS (自旋锁)

article/2025/8/20 15:40:23

文章目录

  • 0. 导言
  • 1. 什么是CAS
  • 2. 保证原子操作
    • 2.1 CAS 实现自旋锁
    • 2.2 AtomicBoolean 中的CAS
    • 2.3 CAS使用场景
  • 3. 锁的分类
    • 3.1 乐观锁
    • 3.2 悲观锁
  • 4. CAS存在的问题
    • 4.1 ABA问题
    • 4.2 循环时间长开销大
    • 4.3 只能保证一个共享变量的原子操作

0. 导言

背景:

我们都知道, 在java语⾔之前, 并发就已经⼴泛存在并在服务器领域得到了⼤量的应⽤ 。所以硬件⼚商⽼早就在芯⽚中加⼊了⼤量支持并发操作的原语, 从⽽在硬件层⾯提升效率。如在intel的CPU中,使⽤cmpxchg指令。

在Java发展初期,java语⾔是不能够利⽤硬件提供的这些便利来提升系统的性能的。⽽随着java不断的发展,Java本地⽅法(JNI)的出现,为java程序越过JVM直接调⽤本地⽅法提供了⼀种便捷的⽅式, 因⽽java在并发的⼿段上也多了起来。⽽在Doug Lea提供的cucurenct包中, CAS理论是它实现整个java包的基⽯。

下面一起来探究下CAS:

说起自旋锁就要从多线程下的锁机制说起,由于在多处理器系统环境中有些资源因为其有限性,有时需要互斥访问(mutual exclusion),这时会引入锁的机制,只有获取了锁的进程才能获取资源访问。即每次只能有且只有一个进程能获取锁,才能进入自己的临界区,同一时间不能两个或两个以上进程进入临界区,当退出临界区时释放锁。

设计互斥算法时总是会面临一种情况,即没有获得锁的进程怎么办?

通常有2种处理方式:

一种是没有获得锁的调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,这就是下面要说:自旋锁。他不用将线城阻塞起来(NON-BLOCKING),他是乐观锁。

另一种是没有获得锁的进程就阻塞(BLOCKING)自己,继续执行线程上的其他任务,这就是 ——互斥锁(包括内置锁Synchronized还有ReentrantLock等等),他是悲观锁。

1. 什么是CAS

CAS,compare and swap的缩写,中文翻译为比较并交换。

CAS 操作中包含三个操作数 —— 内存位置 (V) 、预期原值 (E) 和新值(N)。

这三个字母缩写的含义: Variable:变量;Expect:预期;New:新

如果内存位置的值与预期原值相匹配, 那么处理器会⾃动将 该位置值更新为新值 。否则,处理器不做任何操作。⽆论哪种情况,它都会在 CAS 指令之前返回该位置的值 (在 CAS 的⼀些特殊情况 下将仅返回 CAS 是否成功,而不提取当前值) 。

CAS 用同步的方式是从地址 V 读取值 E, 执⾏多步计算来获得新值 N, 然后使⽤ CAS 将 V 的值从 E 改为 N。如果 V 处的值 尚未同时更改, 则 CAS 操作成功。

CAS 允许算法执⾏读-修改-写操作,而无需害怕其他线程同时修改变量,因为如果其他线程修改变量, 那么 CAS 会检测它(并失败) , 算法可以对该操作重新计算。

执⾏流程如下图所示:
在这里插入图片描述

过程核心:CAS 会判断位置 V 应该包含值E; 如果包含该值, 则将 N 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

2. 保证原子操作

任何技术的出现都是为了解决某些特定的问题, CAS 要解决的问题就是保证原子操作

原子操作是什么?
原子就是最小不可拆分的,原子操作就是最小不可拆分的操作,也就是说操作一旦开始,就不能被打断,直到操作完成。

在多线程环境下,原子操作是保证线程安全的重要手段。

举个栗子来说明一下:
在这里插入图片描述
例如:假设有两个线程在工作,都想对某个值做修改,就拿自增操作来说吧,要对一个整数 i 进行自增操作,需要基本的三个步骤:

1、读取 i 的当前值;
2、对 i 值进行加 1 操作;
3、将 i 值写回内存;

假设两个进程都读取了 i 的当前值,假设是 0,这时候 A 线程对 i 加 1 了,B 线程也 加 1,最后 i 的是 1 ,而不是 2。这就是因为自增操作不是原子操作,分成的这三个步骤可以被干扰。如下面这个例子,10个线程,每个线程都执行 10000 次 i++ 操作,我们期望的值是 100,000,但是很遗憾,结果总是小于 100,000 的。

代码如下

class Plus implements Runnable{static int count = 0;public static void add(){count++;}@Overridepublic void run() {for(int k = 0;k<10000;k++){add();}}
}class Solution {public static void main(String[] args) throws InterruptedException{Thread[] threads = new Thread[10];for(int i = 0;i<10;i++){threads[i] = new Thread(new Plus());threads[i].start();}for(int i = 0;i<10;i++){threads[i].join();}System.out.println(Plus.count);}
}

运行结果显而易见:
在这里插入图片描述

既然这样,那怎么办。没错,也许你已经想到了,可以加锁或者利用 synchronized 同步锁实现,例如,将 add() 方法修改为如下这样:

public static synchronized void add(){count++;
}

运行结果就没问题了
在这里插入图片描述或者,加锁操作,例如下面使用 SynchronizedLock 同步锁,ReentrantLock (可重入锁)实现。

同步锁:

private static Object lock = new Object();
public static void add(){synchronized (lock){count++;}
}

可重入锁:

private static Lock lock = new ReentrantLock();
public static void add(){lock.lock();count++;lock.unlock();
}

结果也都没问题:
在这里插入图片描述

关于原子操作的其他方法可以参考我都另一篇博文:https://blog.csdn.net/weixin_45525272/article/details/125818885

2.1 CAS 实现自旋锁

既然用锁或 synchronized 关键字可以实现原子操作,那么为什么还要用 CAS 呢,因为加锁或使用 synchronized 关键字带来的性能损耗较大,而用 CAS 可以实现乐观锁,它实际上是直接利用了 CPU 层面的指令,所以性能很高。

上面也说了,CAS 是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果。

至于自旋呢,看字面意思也很明白,自己旋转,翻译成人话就是循环,一般是用一个无限循环实现

这样一来,一个无限循环中,执行一个 CAS 操作,当操作成功,返回 true 时,循环结束;当返回 false 时,接着执行循环,继续尝试 CAS 操作,直到返回 true。

扩展:其实 JDK 中有好多地方用到了 CAS ,尤其是java.util.concurrent包下,比如 CountDownLatch、Semaphore、ReentrantLock 中,再比如 java.util.concurrent.atomic 包下,相信大家都用到过 Atomic* ,比如 AtomicBoolean、AtomicInteger 等。

例如:在AtomicInteger 我们可以看到CAS 应用的蛛丝马迹
在这里插入图片描述在这里插入图片描述

2.2 AtomicBoolean 中的CAS

这里拿 AtomicBoolean 来举个例子,因为它简单,如果是AtomicInteger 再深入翻jdk1.8找不到了,后面直接调用起了本地方法:
在这里插入图片描述
所以我们也是只能看AtomicBoolean :

这是 AtomicBoolean 的部分代码,我们看到这里面又几个关键方法和属性:

public class AtomicBoolean implements java.io.Serializable {private static final long serialVersionUID = 4654671469794556979L;// setup to use Unsafe.compareAndSwapInt for updatesprivate static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static {try {valueOffset = unsafe.objectFieldOffset(AtomicBoolean.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}private volatile int value;/*** Returns the current value.** @return the current value*/public final boolean get() {return value != 0;}/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** @param expect the expected value* @param update the new value* @return {@code true} if successful. False return indicates that* the actual value was not equal to the expected value.*/public final boolean compareAndSet(boolean expect, boolean update) {int e = expect ? 1 : 0;int u = update ? 1 : 0;return unsafe.compareAndSwapInt(this, valueOffset, e, u);}
}

1、使用了 sun.misc.Unsafe 对象,这个类提供了一系列直接操作内存对象的方法,只是在 jdk 内部使用,不建议开发者使用;

2、value 表示实际值,可以看到 get 方法实际是根据 value 是否等于0来判断布尔值的,这里的 value 定义为 volatile,因为 volatile 可以保证内存可见性,也就是 value 值只要发生变化,其他线程是马上可以看到变化后的值的

关于volatile 关键字,大家可以查看我的另一篇博文:https://yangyongli.blog.csdn.net/article/details/125819551

3、valueOffset 是 value 值的内存偏移量,用 unsafe.objectFieldOffset 方法获得,用作后面的 compareAndSet 方法;

4、compareAndSet 方法,这就是实现 CAS 的核心方法了,在使用 AtomicBoolean 的这个方法时,只需要传递期望值和待更新的值即可,而它里面调用了 unsafe.compareAndSwapInt(this, valueOffset, e, u) 方法,它是个 native 方法,用 c++ 实现,具体的代码就不贴了,总之是利用了 CPU 的 cmpxchg 指令完成比较并替换,当然根据具体的系统版本不同,实现起来也有所区别,感兴趣的可以自行搜一下相关文章。

2.3 CAS使用场景

(1)CAS 适合简单对象的操作,比如布尔值、整型值等;
(2)CAS 适合冲突较少的情况,如果太多线程在同时自旋,那么长时间循环会导致 CPU 开销很大;

比如 AtomicBoolean 可以用在这样一个场景下,系统需要根据一个布尔变量的状态属性来判断是否需要执行一些初始化操作,如果是多线程的环境下,避免多次重复执行,可以使用 AtomicBoolean 来实现,伪代码如下:

private final static AtomicBoolean flag = new AtomicBoolean();
if(flag.compareAndSet(false,true)){init();
}

比如 AtomicInteger 可以用在计数器中,多线程环境中,保证计数准确。

3. 锁的分类

锁可以分为乐观锁(CAS)和悲观锁(synchronized)。

3.1 乐观锁

CAS(自旋锁):

CAS是从乐观的角度出发,尝试用新值更新内存值,更新时会判断内存值是否被别人修改过,如果没有则直接更新。如果被修改过,则重新获取最新值再继续尝试更新,直到更新成功为止,所以CAS方式也称自旋锁。

3.2 悲观锁

Synchronized(同步锁):
Synchronized是从悲观的角度出发,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。因此Synchronized我们也将其称之为悲观锁。jdk中ReentrantLock也是一种悲观锁。

4. CAS存在的问题

4.1 ABA问题

因为CAS需要在操作值的时候检查下值有没有发⽣变化, 如果没有发⽣变化则更新, 但是如果⼀个值原来是A, 变成了B, ⼜变成了A, 那 么使⽤CAS进⾏检查时会发现它的值没有发⽣变化, 但是实际上却变化了。

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

从Java1.5开始JDK的atomic包⾥提供了⼀个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet⽅法作⽤是⾸先 检查当前引⽤是否等于预期引⽤, 并且当前标志是否等于预期标志,如果全部相等,则以原⼦⽅式将该引⽤和该标志的值设置为给定的更新值。

4.2 循环时间长开销大

⾃旋CAS如果长时间不成功,会给CPU带来⾮常⼤的执⾏开销。如果JVM能⽀持处理器提供的pause指令那么效率会有⼀定的提升

pause指令有两个作⽤:

  • 第⼀它可以延迟流⽔线执⾏指令 (de-pipeline) ,使CPU不会消耗过多的执⾏资源, 延迟的时间取决于具体实现的版本,在⼀些处理器上延迟时间是零。

  • 第二它可以避免在退出循环的时候因内存顺序冲突 (memory order violation) ⽽引起CPU流⽔线被清空 (CPU pipeline flush) ,从⽽提⾼CPU的执⾏效率。

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

当对⼀个共享变量执⾏操作时,我们可以使用循环CAS的⽅式来保证原⼦操作,但是对多个共享变量操作时,循环CAS就⽆法保证操作的 原⼦性。

这个时候就可以⽤锁,或者有⼀个取巧的办法,就是把多个共享变量合并成⼀个共享变量来操作。⽐如有两个共享变量i=2,j=a, 合并⼀下ij=2a,然后⽤CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引⽤对象之间的原⼦性,你可以把多个变量 放在⼀个对象⾥来进⾏CAS操作(详细的大家可以查阅资料)。


http://chatgpt.dhexx.cn/article/QQx5rCMU.shtml

相关文章

CAS和自旋锁

什么是CAS CAS算法&#xff08;Compare And Swap&#xff09;&#xff0c;即比较并替换&#xff0c;是一种实现并发编程时常用到的算法&#xff0c;Java并发包中的很多类都使用了CAS算法。 CAS算法有3个基本操作数&#xff1a; 内存地址V旧的预期值A要修改的新值B CAS使用自…

Java中的自旋锁,手动实现一个自旋锁

自旋锁 CAS是实现自旋锁的基础,CAS利用CPU指令保证了操作的原子性,已达到锁的效果。自旋是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁, 当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环…

学习自旋电子学的笔记04:模拟自旋波在弯曲磁畴壁中传播

文章目录 前言零、笔记03中错误的补充改正1.保持电子的极化方向不变的原因2.Oxs_SpinXferEvolve类的额外补充说明3.时间演化器的时间步长相关补充说明 一、文章概述和要复现的微磁模拟1.文章概述2.要复现的微磁模拟 二、FIG.1三、 FIG.21. FIG.2(a-b)2. FIG.2(c-f) 四、 FIG.3五…

CAS和自旋到底是一个概念吗?

问题: CAS是 compare and swap ,就是一个比较工作内存和主内存的值是否相同&#xff0c;相同的话&#xff0c;就用新值来替换这么一个操作。 但是&#xff0c;为什么好多地方都说这是自旋呢&#xff1f; 我理解比较一次的话&#xff0c;成功就返回true了&#xff0c;失败&am…

CAS及CAS自旋

1. CAS简介 比较并交换(compare and swap, CAS)&#xff0c;是原子操作的一种。在多线程没有锁的状态下&#xff0c;可以保证多个线程对同一个值的更新。 CAS可用于在多线程编程中实现不被打断的数据交换操作&#xff0c;从而避免多线程同时改写某一数据时由于执行顺序不确定…

自旋玻璃(spin glass)、自旋冰(spin ice)和量子自旋液体(quantum spin liquid)(之二)

文章目录 13. 几何阻挫&#xff08;Geometrical frustration&#xff09;13.1 磁序&#xff08;Magnetic ordering&#xff09;13.2 数学定义13.3 水冰&#xff08;water ice&#xff09;13.4 Paulings model 的扩展&#xff1a;广义的阻挫13.5 人工几何阻挫铁磁体13.6 没有晶格…

CAS自旋

文章目录 1. CAS简介2. CAS的特点3. 自旋–比较和交换4. 什么是ABA问题5. ABA问题怎么解决6. 悲观锁7. 乐观锁8. CAS锁升级 CAS面试提问环节&#xff1a; synchronized、ReentrantLock、CAS全家桶发售HashMap、Hashtable、ConcurrentHashMap组合拳出击 1. CAS简介 比较并交换(…

自旋玻璃(spin glass)、自旋冰(spin ice)和量子自旋液体(quantum spin liquid)(之一)

文章目录 1. Giorgio Parisi 简介2. 复杂无序系统2.1 相变、序参量与对称性破缺2.2 复杂系统 3. 自旋玻璃简介3.1 自旋冻结3.2 亚稳态3.3 磁化弛豫3.4 玻璃化和无序系统3.5 Ising model3.6 自旋玻璃模型3.7 自旋玻璃相变 4. 磁场中的现象5. Edwards-Anderson model6. Sherringt…

office卸载工具怎么用(官方干净卸载方法)

https://jingyan.baidu.com/article/39810a23593f37b636fda60d.html

Office卸载安装问题

卸载Office 问题描述 此前已安装过新的Office&#xff0c;按照正常的卸载流程卸载后&#xff08;控制面板卸载后&#xff09;&#xff0c;安装新的Office时&#xff0c;提示&#xff1a; 无法安装64位版本的Office&#xff0c;因为在电脑上已有32位程序。如下图所示。 **解…

32位office卸载不干净怎么办如何删除32位Office

以前在电脑上安装了32位系统的Office&#xff0c;现在想要换成64位的Office&#xff0c;但是在安装的时候提示无法进行安装&#xff0c;需要先卸载以前的32位Office&#xff0c;出现这种情况怎么办呢&#xff1f;如何彻底卸载干净32位系统的Office呢&#xff1f;下面就一起来看…

Office卸载不干净,注册表项权限修改后仍然无法删除的问题

Office卸载不干净&#xff0c;注册表项权限修改后仍然无法删除的问题 针对卸载Office最极端的情况&#xff0c;试试以下方法。 1.卸载开始菜单的office; 可以借助以下工具进行进行清除&#xff0c;完全卸载&#xff08;但是对本人无效&#xff09; 链接&#xff1a;https://…

mac m1 office卸载重装(学校官方正版)

卸载office 1、应用程序中将所有的microsoft相关软件移到废纸篓&#xff0c;可能还有outlook或者onedrive&#xff0c;清空废纸篓 2、cmdshiftG&#xff0c;输入/Library/Preferences&#xff0c;删除所有com.Microsoft开头的文件 3、进入/Library/PrivilegedHelperTools&…

卸载32位office安装64位office卸载不完全导致不能安装64位office时解决办法

转载自https://blog.csdn.net/zzfenglin/article/details/60780831 问题描述 安装64位office办公软件的时候提示已经安装32位的office办公软件所以无法继续安装&#xff0c;但实际上之前安装的32位的office办公软件已经卸载了。问题现象截图如下&#xff1a; 解决办法 从问题描…

office2019 完美卸载

记录下我日常手贱的经历。 事情的起因呢&#xff0c;是在家办公的时候呢&#xff0c;突然要写一份文档&#xff0c;然后呢&#xff0c;我的wps过期了&#xff0c;只能看&#xff0c;而不能编辑。然后我就打算装个office2019感受下。结果我就卸载了原来安装过但是已经过期了的o…

Office uninstall(专业office卸载工具)绿色单文件版V1.8.3 全面清除office卸载残留

Office uninstall 是一款专门为微软Office办公软件量身定做的office卸载工具&#xff0c;可以帮助大家彻底卸载已经安装到电脑上的Office软件&#xff0c;彻底解决office卸载不干净&#xff0c;无法重新安装的问题&#xff0c;全面兼容office2003、office2007、office2010、off…

Win10预装Office卸载工具

联想软件远程服务&#xff0c;让您足不出户&#xff0c;轻松解决电脑问题&#xff01;软件调试、电脑加速、游戏加速、重装系统、修复浏览器、安装驱动&#xff0c;远程or上门&#xff1f;联想专家一对一服务任您挑选&#xff01; 重要提示&#xff1a;您需要在电脑端下载并运行…

将office办公软件彻底从计算机中卸载清除

此情况针对于计算机系统中可能包含两个版本或更多的office同时存在导致在新建各种文档时会有些冲突&#xff0c;卸载不干净后&#xff0c;重新安装回去也还是无济于事&#xff0c;所以经过探索&#xff0c;将成功的方法发布给大家&#xff0c;希望对大家有所帮助。 有两种用于彻…

关于机器学习SVM中KKT条件的深入理解推导

关于机器学习SVM中KKT条件的深入理解推导 目前为止的已知KKT条件违反KKT条件的情况参考文献 本文面向在寻找KKT条件相关推到文章的读者&#xff0c;且默认前面关于svm的松弛下的模型和smo算法推到都已经了解。如果没有或者需要温习&#xff0c;请参看支持向量机SVM与SMO算法的的…

理解KKT条件

一、引言 对于无约束最优化问题&#xff0c;其搜索空间是无界的&#xff0c;只要确定了搜索方向和步长因子&#xff0c;便可以在一轮或几轮迭代之后找到最优解或近似最有解。这里举个不太恰当的例子&#xff0c;无约束最优化如同在浩瀚的宇宙中寻找体积最大的星球&#xff0c;你…