目录
- CAS原理解析
- java CAS简介
- 代码说明
- 执行过程
- CAS底层原理
- 源码调试
- CAS存在的问题
- 最后
CAS原理解析
java CAS简介
- CAS全称:Compare-And-Swap,即比较并替换。比较变量现在的值和以前的值是否一致,若一致则替换,否则不替换
- CAS作用:原子性更新变量值,保证线程安全
- CAS指令:需要有三个操作数,变量的当前值(V),旧的预期值(A),准备设置的新值(B)
- CAS指令执行条件:当前仅当V=A时,处理器才会设置V=B否则不执行更新
- CAS的返回值:V的之前值
- CAS处理过程:原子操作,执行期间不会被其他线程中断,线程安全
- CAS并发原语:体现在Java语言中sun.misc.Unsafe类的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令,这是一种完全依赖于硬件的功能,通过它实现了原子操作。由于CAS是一种系统原语,原语属于操作系统用于范畴,是由
若干条指令
组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,所以CAS是一条CPU的原子指令,不会造成所谓的数据不一致的问题,所以CAS是线程安全的。
代码说明
这里我们使用Atomiclnteger来演示:
public class CASDemo {
public static void main(String[] args) {
// 首先设置变量的初始值为10,主内存中的值设置为10
AtomicInteger atomicInteger = new AtomicInteger(10);
// 调用atomicInteger的CAS方法,先比较当前值是否为10,如果是则替换为20,不是则不替换
Boolean result1 = atomicInteger.compareAndSet(10,20);
System.out.printf("当前atomicInteger变量的值:%d 比较结果%s\r\n", atomicInteger.get(), result1);
Boolean result2 = atomicInteger.compareAndSet(10,30);
System.out.printf("当前atomicInteger变量的值:%d, 比较结果%s\n" , atomicInteger.get(), result2);
}
}
结果:
当前atomicInteger变量的值:20 比较结果true
当前atomicInteger变量的值:30, 比较结果true
执行过程
- 第一步:线程1和线程2都有主内存中变量的拷贝,值都为10
- 第二步:线程1想要将值更新为20,先要将工作内存中的变量值与主内存中的变量进行比较,值都等于10,所以可以将内存找那个的值替换为20
- 第三步:线程1将主内存中的值替换成20,并将线程1 中的工作内存中的副本更新为20
- 第四步:线程2想要将变量更新为30,先要将线程2的工作内存中的值与主内存进行比较10不等于20,所以不能更新
- 第五步:线程2将工作内存的副本更新为与主内存一致:20
CAS底层原理
源码调试
这里我们用atomicInteger的getAndIncrement()方法来讲解,这个方法里面涉及到了比较并替换的原理。
源码:
public class CASDemo {
public static void main(String[] args) throws InterruptedException{
AtomicInteger atomicInteger = new AtomicInteger(10);
Thread.sleep(100);
new Thread(()->{
atomicInteger.getAndIncrement();
},"zhonghu").start();
atomicInteger.getAndIncrement();
}
}
我们用debug模式进行调试,看这个方法里面到底做了什么操作,首先在第九行打断点,此时子线程zhonghu还没执行自增操作。
getAndIncrement方法会调用unsafe的getAndAddInt方法
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
在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;
}
在361行打上断点,让程序执行到这个位置
此时:
- var1:当前对象,我们定义的atomicInteger
- var2:当前对象的内存偏移量
- var4:当前自增多少,默认为1,且不可设为其他值
- var5:当前变量的值
- this.getIntVolatile(var1,var2):根据当前对象var1和对象的内存偏移量var2得到主内存中变量的值,赋值给var5,并在main线程的工作内存中存放一份var5的副本
继续往下走一步,var获取到主内存中的值为10。
然后切换到子线程zhonghu,还在361行断点处,还未获取到主内存的值
子线程继续执行一步,获取到var5的值等于10
切换到main线程,进行比较并替换
先比较线程中的副本是否与主内存相等,相等则可以进行自增,并返回 副本的值,若其他线程修改了主内存中的值,当前线程不能进行自增,需要重写获取主内存的值,然后再次判断是否与主内存中的值是否相等,以此往复
CAS存在的问题
大家可以发现,在子线程中可以会出现循环多次的问题,因为其他线程可能将主内存的值又改了。但子线程还是拿到的是老的数据,就回出现再循环一次,就给CPU带来性能开销,这就是自旋
- 频繁出现自旋,循环时间长,开销大(因为执行的是do-while,如果比较不成功则hi一直循环,最差的情况,就是某个线程一直取到的值和预期值不一样,这样就会无限循环下去)
- 只能保证一个共享变量的原子操作
- 当对一个共享变量执行操作时,我们可以通过CAS的方式来保证原子操作
- 但是对于多个共享变量操作时,喜欢CAS就无法保证操作的原子性,这和个时候只能用锁来保证原子性
最后
- 如果觉得看完有收获,希望能给我点个赞,这将会是我更新的最大动力,感谢各位的支持
- 欢迎各位关注我的公众号【java冢狐】,专注于java和计算机基础知识,保证让你看完有所收获,不信你打我
- 如果看完有不同的意见或者建议,欢迎多多评论一起交流。感谢各位的支持以及厚爱。