CAS底层原理

概念

CAS的全称是Compare-And-Swap,它是CPU并发原语

它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的

CAS并发原语体现在Java语言中就是sun.misc.Unsafe类的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令,这是一种完全依赖于硬件的功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统应用范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致的问题,也就是说CAS是线程安全的。

代码使用

首先调用AtomicInteger创建了一个实例, 并初始化为10

// 创建一个原子类
AtomicInteger atomicInteger = new AtomicInteger(10);

然后调用CAS方法,企图更新成2020,这里有两个参数,一个是10,表示期望值,第二个就是我们要更新的值

atomicInteger.compareAndSet(10, 2020)

然后再次使用了一个方法,同样将值改成2021

atomicInteger.compareAndSet(10, 2021)

完整代码如下:

/**
* CAS 比较并交换
* @author xiao
* @date 2020/4/23 9:14
*/
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(10);
/**
* 一个是期望值,一个是更新值,但期望值和原来的值相同时,才能够更改
* 假设拿的是10,也就是expect为5,然后我需要更新成 2020
*/
System.out.println(atomicInteger.compareAndSet(10, 2020)+"\t当前的值为:"+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(10, 2021)+"\t当前的值为:"+atomicInteger.get());
}
}

上面代码的执行结果为

这是因为我们执行第一个的时候,期望值和原本值是满足的,因此修改成功,但是第二次后,主内存的值已经修改成了2020,不满足期望值,因此返回了false,本次写入失败

这个就类似于SVN或者Git的版本号,如果没有人更改过,就能够正常提交,否者需要先将代码pull下来,合并代码后,然后提交

CAS底层原理

首先我们先看看 atomicInteger.getAndIncrement()方法的源码

从这里能够看到,底层又调用了一个unsafe类的getAndAddInt方法

1、unsafe类

Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(Native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存数据。Unsafe类存在sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中的CAS操作的执行依赖于Unsafe类的方法。

注意Unsafe类的所有方法都是native修饰的,也就是说unsafe类中的方法都直接调用操作系统底层资源执行相应的任务

为什么Atomic修饰的包装类,能够保证原子性,依靠的就是底层的unsafe类

2、变量valueOffset

表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

从这里我们能够看到,通过valueOffset,直接通过内存地址,获取到值,然后进行加1的操作

3、变量value用volatile修饰

保证了多线程之间的内存可见性

var5:就是我们从主内存中拷贝到工作内存中的值

那么操作的时候,需要比较工作内存中的值,和主内存中的值进行比较

假设执行 compareAndSwapInt返回false,那么就一直执行 while方法,直到期望的值和真实值一样

  • val1:AtomicInteger对象本身
  • var2:该对象值得引用地址
  • var4:需要变动的数量
  • var5:用var1和var2找到的内存中的真实值
    • 用该对象当前的值与var5比较
    • 如果相同,更新var5 + var4 并返回true
    • 如果不同,继续取值然后再比较,直到更新完成

这里没有用synchronized,而用CAS,这样提高了并发性,也能够实现一致性,是因为每个线程进来后,进入的do while循环,然后不断的获取内存中的值,判断是否为最新,然后在进行更新操作。

假设线程A和线程B同时执行getAndInt操作(分别跑在不同的CPU上)

  1. AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的 value 为3,根据JMM模型,线程A和线程B各自持有一份价值为3的副本,分别存储在各自的工作内存
  2. 线程A通过getIntVolatile(var1 , var2) 拿到value值3,这是线程A被挂起(该线程失去CPU执行权)
  3. 线程B也通过getIntVolatile(var1, var2)方法获取到value值也是3,此时刚好线程B没有被挂起,并执行了compareAndSwapInt方法,比较内存的值也是3,成功修改内存值为4,线程B打完收工,一切OK
  4. 这是线程A恢复,执行CAS方法,比较发现自己手里的数字3和主内存中的数字4不一致,说明该值已经被其它线程抢先一步修改过了,那么A线程本次修改失败,只能够重新读取后在来一遍了,也就是在执行do while
  5. 线程A重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。

Unsafe类 + CAS思想: 也就是自旋,自我旋转

底层汇编

Unsafe类中的compareAndSwapInt是一个本地方法,该方法的实现位于unsafe.cpp中

  • 先想办法拿到变量value在内存中的地址
  • 通过Atomic::cmpxchg实现比较替换,其中参数X是即将更新的值,参数e是原内存的值

CAS缺点

CAS不加锁,保证一次性,但是需要多次比较

  • 循环时间长,开销大(因为执行的是do while,如果比较不成功一直在循环,最差的情况,就是某个线程一直取到的值和预期值都不一样,这样就会无限循环)
  • 只能保证一个共享变量的原子操作
    • 当对一个共享变量执行操作时,我们可以通过循环CAS的方式来保证原子操作
    • 但是对于多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候只能用锁来保证原子性
  • 引出来ABA问题?

ABA问题

总结

CAS

CAS是compareAndSwap,比较当前工作内存中的值和主物理内存中的值,如果相同则执行规定操作,否者继续比较直到主内存和工作内存的值一致为止

CAS应用

CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则不操作

CAS原理解析的更多相关文章

  1. TS各个表 与 SECTION 的解析 CAS原理

    TS流,通过一个个的TS包来传送: TS包可以是传送PSI SI等各表的数据包,也可以是传送节目音视频数据(携带的PES包:音视频基本流包)的包:TS携带 PSI SI等表的数据时,各个表以各表对应的 ...

  2. JAVA CAS原理深度分析-转载

    参考文档: http://www.blogjava.net/xylz/archive/2010/07/04/325206.html http://blog.hesey.net/2011/09/reso ...

  3. 【转】JAVA CAS原理深度分析

    java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包.可见CAS的重要性. CAS CAS:Compare and Swap, 翻译成比较并交换. java.uti ...

  4. JAVA CAS原理深度分析

    参考文档: http://www.blogjava.net/xylz/archive/2010/07/04/325206.html http://blog.hesey.net/2011/09/reso ...

  5. JAVA CAS原理深度分析(转)

    看了一堆文章,终于把JAVA CAS的原理深入分析清楚了. 感谢GOOGLE强大的搜索,借此挖苦下百度,依靠百度什么都学习不到! 参考文档: http://www.blogjava.net/xylz/ ...

  6. JAVA CAS原理浅谈

    java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包.可见CAS的重要性. CAS CAS:Compare and Swap, 翻译成比较并交换. java.uti ...

  7. 转:JAVA CAS原理深度分析

    看了一堆文章,终于把Java CAS的原理深入分析清楚了. 感谢GOOGLE强大的搜索,借此挖苦下百度,依靠百度什么都学习不到! 参考文档: http://www.blogjava.net/xylz/ ...

  8. 【漫画】CAS原理分析!无锁原子类也能解决并发问题!

    本文来源于微信公众号[胖滚猪学编程].转载请注明出处 在漫画并发编程系统博文中,我们讲了N篇关于锁的知识,确实,锁是解决并发问题的万能钥匙,可是并发问题只有锁能解决吗?今天要出场一个大BOSS:CAS ...

  9. Java并发包JUC核心原理解析

    CS-LogN思维导图:记录CS基础 面试题 开源地址:https://github.com/FISHers6/CS-LogN JUC 分类 线程管理 线程池相关类 Executor.Executor ...

随机推荐

  1. Codeforces Add on a Tree

    Add on a Tree time limit per test 1 second memory limit per test 256 megabytes input standard input ...

  2. AQS机制

    一,Lock接口 锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁).在Lock接口出现之前,Java ...

  3. 3 report formats of SFDC

    Choose one of the following report formats using the Format menu of the report builder. Tabular form ...

  4. LoardPe与Import REC X64dbg脚本 脱壳 Upx

    目录 LoardPe与Import REC X64dbg脚本 脱壳 Upx 一丶X64dbg调试器与脚本 1.1 起因 1.2 脚本的调试 1.3 Upx脱壳脚本 二丶LoardPe 内存Dump与I ...

  5. VBScript - 动态 Array 实现方法大全!

    记录一些方法,关于 VBScript 中,动态 Array 的实现 ,也适用于 VBA, 很久以前,写 VBA 的时候,就觉得使用 Array 很不方便,因为大小固定, 当时想的是,要是 Array ...

  6. python框架-Django创建项目

    创建项目 django-admin startproject douban//创建project cd douban python manage.py startapp books//创建app项目 ...

  7. 纯css实现图片或者页面变灰色

    前言 今天是个沉痛的日子,全国哀悼英雄,各大平台平日鲜丽的界面置纷纷换成了灰色,以表对逝者的安息与尊敬!!对付疫病,我没多大的本事,只能记录一点点知识来提升自己擅长的技术,待到将来能为国家尽一份绵薄之 ...

  8. GitHub 热点速览 Vol.14:周获 2k+ Vim 掀起三维编程风

    作者:HelloGitHub-小鱼干 摘要(用于 公众号/博客园等地方)寓教于乐,应该是上周 Trending 的主题了,无论是被多人转发推荐的三维 Vim 项目 Vim³ 或者是流体运动的 WebG ...

  9. jmeter执行多条sql语句

    1.JDBC Connection Configuration 在配置DataBase URL的时候,加上allowMultiQueries=true 2.在JDBC Request中设置Quer T ...

  10. 1058 A+B in Hogwarts (20分)(水)

    If you are a fan of Harry Potter, you would know the world of magic has its own currency system -- a ...