李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
CAS简介
Leefs
2020-02-22 PM
2475℃
0条
# CAS简介 ### 一、CAS概念 CAS的全称为Compare And Swap即比较并交换,它是一条CPU并发原语。 它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值。看起来这是两步操作,但是由于底层硬件的支持,使两步操作能一步完成,从而保证了原子性,避免了独占锁的资源浪费。 CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。 **CAS操作包含三个操作数--内存位置(V)、预期原值(A)和新值(B)。**如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。 **代码示例** ```java public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(5); System.out.println(atomicInteger.compareAndSet(5,2019)+"\t current data:"+atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(5,2020)+"\t current data:"+atomicInteger.get()); } } ``` **运行结果** ```java true current data:2019 false current data:2019 ``` **分析** > compareAndSet(v1,v2) > > v1:预期值 > > v2:修改后的值 首先创建AtomicInteger对象,并为其附初始值5,当第一次调用AtomicInteger对象的compareAndSet方法时因为AtomicInteger初始值为5与预期值相同,所以返回true,将之前的初始值5改成2019。第二次调用AtomicInteger对象的compareAndSet方法因为预期值5与初始值2019不相同,所以返回false,对之前的初始值不做任何操作。 ### 二、CAS底层实现 CAS主要是通过Unsafe类来操作底层硬件来实现原子性。 ```java public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates 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; //value是volatile修饰的,保证了线程之间的可见性 ``` **UnSafe类**是CAS的核心类,由于Java方法无法直接访问底层,需要通过本地(native)方法来访问,基于该类可以直接操作特定的内存数据。 UnSafe类在于sun.misc包中,其内部方法操作可以向C指针一样直接操作内存,因为Java中CAS操作依赖于UNSafe类的方法。注意UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务 > + 变量ValueOffset:它是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的 > + 变量value:被volatile修饰,保证了多线程之间的可见性。 **Unsafe类中的getAndAddInt源码解读** ```java 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; } ``` > + var1:AtomicInteger对象本身 > + var2:该对象值的引用地址 > + var4:需要变动的数值 > + var5:是用var1、var2找出内存中的值 用该对象当前的值与var5比较 如果相同,更新var5的值并且返货true 如果不同,继续取值然后比较,直到更新完成 **执行过程分析** 假设线程A和线程B两个线程同时执行getAndAddInt操作(分别在不同的CPU上): 1. 1.AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存. 2. 2.线程A通过getIntVolatile(var1,var2) 拿到value值3,这时线程A被挂起. 3. 3.线程B也通过getIntVolatile(var1,var2) 拿到value值3,此时刚好线程B没有被挂起并执行compareAndSwapInt方法比较内存中的值也是3 成功修改内存的值为4 线程B执行完成 4. 4.这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的数值和内存中的数字4不一致,说明该值已经被其他线程抢先一步修改了,那A线程修改失败,只能重新来一遍了 5. 5.程A重新获取value值,因为变量value是volatile修饰,所以其他线程对他的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt方法进行比较替换,直到成功. ### 三、CAS缺点 1. 1.循环时间开销容易太大 因为CAS方法内部有do-while循环 ``` 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; } ``` 如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。 2. 2.只能保证一个变量的原子性 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。 ### 总结 CAS(Compare-And-Swap):比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较指导主内存和工作内存中的值一致为止。 CAS应用 CAS有三个操作数,内存值V,旧的预期值A,要修改的更新值B。 当且仅当预期值A和内存值V相同,将内存值V修改为B,否则什么都不做。
标签:
Java
,
并发编程
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/637.html
上一篇
单例模式volatile分析
下一篇
CAS的ABA问题及解决
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
Stream流
VUE
序列化和反序列化
Java
递归
MyBatis-Plus
Netty
Livy
Hive
正则表达式
前端
DataX
JavaSE
RSA加解密
Typora
Spark RDD
哈希表
Java阻塞队列
链表
Tomcat
排序
DataWarehouse
Spark
并发编程
Azkaban
线程池
Filter
Hadoop
Yarn
Spark Core
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭