CAS 比较并交换
简介
CAS 的全称为 Compare-And-Swap,他是一条 CPU 并发源语。
他的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
CAS 并发原语体现在 JAVA 语言中就是 sun.misc.Unsafe 类中的各个方法。调用 UnSafe 类中的 CAS 方法,JVM 会帮我们实现出 CAS 汇编指令
。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题
。
代码演示
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2022) + "=====" + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 2014) + "=====" + atomicInteger.get());
}
}
原理讲解
CAS 就是基于 unsafe 类去实现的,底层汇编。
源码解析
截取了 AtomicInteger 类中的部分代码
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
/**
* Atomically increments by one the current value.
* this:当前对象
* valueOffset:内存偏移量
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
Unsafe 中的部分源代码
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;
}
Unsafe
是 CAS 的核心类,由于 Java 方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe 相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe 内存在于 sun.mics 包中,其内部方法操作可以像C的指针一样直接操作内存,因为 Java 中 CAS 操作得知执行依赖于 Unsafe 类的方法。
注意 Unsafe 类中所有的方法都是 native 修饰的,也就是说 Unsafe 类中的方法都是直接调用操作系统底层资源执行相应任务。
valueOffset 属性
表示该变量在内存中的偏移地址,因为 Unsafe 就是根据内存偏移地址获取数据的。
/**
* Atomically increments by one the current value.
* this:当前对象
* valueOffset:内存偏移量
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
使用 volatile 修饰保证了可见性,一旦修改其他类可以看见。
private volatile int value;
代码执行步骤讲解
假设线程A和线程B两个线程同时执行getAndAddlnt操作(分别跑在不同CPU上):
- AtomicInteger 里面的value原始值为5,即主内存中 AtomicInteger 的 value 为 5,根据JMM 模型,线程 A 和线程 B 各自持有一份值为 5 的 value 的副本分别到各自的工作内存。
- 线程 A 通过 getlntVolatile(var1,var2)拿到value值5,这时线程A被挂起。
- 线程 B 也通过 getlntVolatile(var1,var2)方法获取到value值5,此时刚好线程 B 没有被挂起并执行 compareAndSwaplnt 方法比较内存值也为 5,成功修改内存值为6,线程B打完收工,一切OK。
- 这时线程 A 恢复,执行 compareAndSwaplnt 方法比较,发现自己手里的值数字 5 和主内存的值数字 6 不一致,
说明该值己经被其它线程抢先一步修改过了,那A线程本次修改失败,只能重新读取重新来一遍了
。 - 线程 A 重新获取 value 值,因为变量 value 被 volatile 修饰,所以其它线程对它的修改,线程 A 总是能够看到,线程 A 继续执行 compareAndSwaplnt 进行比较替换,直到成功。
本文作者:李小龙他哥
本文链接:https://www.cnblogs.com/lhnstart/p/16002293.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?