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问题,改用传统的互斥同步可能会比原子类更高效。

相关内容

热门资讯

6000具乌军遗骸放在边境没人... 在隐蔽战线上吃了一个大亏后,俄罗斯终于在舆论战上扳回一局。 这几天俄罗斯外交部和媒体一直在大肆宣扬一...
原创 快... 本来以为坐下来享受美食,没想到却成了餐桌上的“菜”。谎言说着说着,居然成了现实。用这两句话来形容印度...
原创 中... 据报道称,路透社近日援引三名消息人士报道称,随着出台出口管制政策,中国已对稀土磁铁行业引入了跟踪系统...
绿会法工委对《云南省陆生野生动... 近期,云南人大官网发布《云南省陆生野生动物保护条例(修订草案)》(以下简称《修订草案》),并向社会公...
亏损加剧、商业化遇阻,氢燃料电... 记者 周信 “我们现在的确很难,现在燃料电池企业都面临着比较严峻的挑战,一方面燃料电池企业造血能力较...
原创 输... 据央视新闻报道,有记者就韩国总统李在明正式宣誓就职提问。外交部发言人表示,中国领导人已经向李在明总统...
原创 马... 据北京日报报道,关于法国总统马克龙在香格里拉对话会期间的言论,中国驻法国大使馆发言人表示,我们坚决反...
“两高”联合印发《通知》完善对... 央视网消息:近日,最高人民法院、最高人民检察院联合印发《关于完善对海事法院法律监督机制的通知》(下称...
北大“韦神”粉丝破2000万!... 6月5日晚,北京大学的韦东奕老师、网友熟悉的“韦神”开通个人社交平台账号,发布视频和网友打招呼,吸引...