CAS简单理解
创始人
2025-05-31 23:16:57
0

CAS

在介绍CAS之前,我们先看一段代码

@Slf4j
public class CASTest {public static volatile int race = 0;public static final int THREAD_COUNT = 20;public static void increase(){race++;}//20个线程private static final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT,new ThreadNameFactory());public static void main(String[] args) throws InterruptedException {for (int i = 0; i < THREAD_COUNT; i++) {log.debug("第{}个线程",i);//20 * 10,000 = 200,000  输出结果不为200000executorService.execute(new ThreadIncrease());}Thread.sleep(10000);System.out.println(race);}//线程工厂,用来定义线程名称static class ThreadNameFactory implements ThreadFactory{private static final AtomicInteger atomicInteger = new AtomicInteger(0);@Overridepublic Thread newThread(Runnable r) {return new Thread(r,atomicInteger.getAndIncrement()+"");}}//线程执行方法体static class ThreadIncrease implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10000; i++) {increase();}log.debug("第{}个线程运行结束",Thread.currentThread().getName());}}
}

这段代码,无论怎么运行,结果多不会是20000(可能某一次误打误撞会是),这是为什么?

我们知道,volatile关键字是一个轻量级的同步框架,在运行时时,使用volatile修饰的变量的改变对其他线程是可见的。

但是volatile只保证了可见性,没有保证原子性,所以答案就不会是200000。

想让这段代码运行成功很简单,在increase()方法上加sychnorized,但是这样性能会大大降低。

那该怎么做呢?使用原子操作类的变量就可了,以下是更改后的代码:

	public static AtomicInteger race = new AtomicInteger(0);public static void increase(){race.getAndIncrement();}

再次运行,这次结果就是200000了。

那么原子操作类是怎么保证运行的呢?我们先看getAndIncrement()的源码:

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

我们接着往下看,看getAndAddInt这个方法:

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;
}

在这段代码中,有用的其实就是这个方法compareAndSwapInt(),他就是我们要介绍的CAS了

compareAndSwapInt()方法解析:

拿到内存位置的新值var5,使用CAS尝试修改为var5+var4,如果失败,获取新值var5,然后继续,直到成功

介绍

CAS是CompareAndSwap的缩写,比较并替换。CAS底层有三个操作数:

  • 内存地址V
  • 旧值A
  • 即将要更改的值B

CAS在运行时,当且仅当内存地址的V的值与预期值A相等,才会将内存地址对应的V值改为B,具体流程如下:

  • 线程1的内存地址V的值为10,此时线程1想要把V的值修改为11,所以此时A = 10,B=11
  • 但是在线程1提交之前,线程2抢先一步提交了,把V的值设置为11了,此时线程1将提交失败(因为A=10 != V=11)
  • 而后线程1重新获取内存地址V值,并重新计算要修改的值,假设将修改为12,那么此时V=11,A=11,B=12
  • 此时进行对别,V=A==11,所以提交成功,V被修改为12
  • 到此CAS操作完成

在上述过程中,程1重新获取内存地址V值,并重新计算要修改的值的过程称为自旋

从思想上来说,CAS属于乐观锁,认为程序并发情况没那么严重,所以会尝试不断更新,而synchronized被称为悲观锁

缺点

CAS虽然高效的解决了原子操作问题,但是CAS仍然存在三个大问题:

  • 循环时间长,开销大

    • 如果不成功,会一直循环,给CPU带来很大的开销
  • 只能保证一个共享变量的原子操作

    • 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
  • ABA问题:

    • CAS的过程如下:

      1. 从地址V中读取A值
      2. 根据A计算B
      3. 通过CAS将V修改为B

      但是在第一步中读取的是A,并且在第三步修改成功,但是第二步呢?可不可能被其他线程修改过?答案是肯定的,如果这期间被第二个线程修改过,他的值被改为了B,后来又经过又被改为A,那么CAS就会认为他从来没被改变过(其实目的还是为了改成A),所以继续又改成了B(显然不是我们想要的),这个问题就被称为ABA问题

      Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

相关内容

热门资讯

嘉兴男子与妻争吵,突然将行李箱... 近日,浙江嘉兴一对夫妻因琐事发生争吵,丈夫突然将装满衣物的行李箱从6楼扔到楼下,引发关注。11月22...
三地107家律所齐聚丰台,京津... 11月22日,京津冀律师驿站举办“党建业务深度融合 促进行业规范发展”主题活动,发布“百千万行动计划...
家装预付资金安全困局如何破解,... 家装预付资金安全困局如何破解 专家提出:建立“先验收后付款”装修资金存管制度 预交数万元甚至数十万元...
工行安康解放路支行积极开展《反... 为深入贯彻落实《国家金融监督管理总局安康监管分局办公室关于开展<反有组织犯罪法>宣传活动的通知》要求...
重庆公布育儿补贴制度实施方案 原标题:每孩每年3600元 重庆公布育儿补贴制度实施方案 11月21日,记者了解到,市卫生健康委、市...
十五运会组委会在深总结本届赛事... 深圳新闻网2025年11月22日讯(深圳报业集团记者 林炜航)11月21日,十五运会组委会在深圳市民...
中国军视网:日本妄言击沉福建舰... 本文转自【中国军视网】; 日本首相高市早苗发表涉台错误言论,公然挑战一个中国原则,甚至还有日本无知政...
重磅!东莞长安50万㎡产城发布... 在当下竞争激烈的市场环境中,中小企业如何突破成本压力,找到一片既能扎根成长又能眺望未来的理想栖息地?...
毕马威:政策、资本等多维着力 ... 由毕马威联合长三角G60科创走廊创新研究中心主办的“长三角高端装备新质领袖榜单发布仪式”于11月21...