CAS是什么?
CAS ==> CompareAndSwap ==> 比较再交换
public static void main(String[] args){
AtomicInteger atomicInteger = new AtomicInteger(5);
//main do thing
System.out.println(atomicInteger.compareAndSet(5,2019)+"\t current data:"+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5,1024)+"\t current data:"+atomicInteger.get());
}
当主物理内存中的值,和传入的第一个值一样时,返回true(比较),则更新物理内存中的值为传入的第二个值(交换)。
当主物理内存中的值,和传入的第一个值不同时,返回false(比较),则本次修改失败。需要重新获得物理内存中的真实值。
CAS(CompareAndSwap),是一条CPU并发原语
。
它的功能是判断内存某个位置的值,是否为预期值,如果是则更新为新的值,这个过程是原子的。
CAS并发原语体现在JAVA语言中,就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于 硬件
的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU原子指令,不会造成所谓的数据不一致问题。
假设线程A和线程B两个线程同时执行getAndAddInt操作(分别跑在不同CPU上):
- AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本,分别到各自的工作内存。
- 线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。
- 线程B也通过getIntVolatile(var1, var2)方法获取到value值3,此时刚好线程B
没有被挂起
,并执行compareAndSwapInt方法,比较内存值也为3,成功修改内存值为4,线程B打完收工,一切OK - 这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值数字3和主内存的值数字4不一致,说明该值已被其他线程抢先一步修改过了,那A线程本次修改失败,
只能重新读取重新来一遍了
。 - 线程A重新获取value值,因为变量value被volatile修饰,所以其他线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。
CAS应用:
CAS有三个操作数,内存值V,旧的预期值A,要修改的更新值B。
当且仅当与气质A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS缺点:
-
循环时间长开销很大(CAS失败会一直尝试,若长时间不成功,会给CPU带来很大开销)
-
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,
但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。 -
引出ABA问题。
CAS——>UnSafe——>CAS底层思想——>ABA——>原子引用更新——>如何规避ABA问题
ABA问题:
假设有两个线程A和B,A线程完成工作慢,假设为10s,B完成较快,假设为2S,假设两个线程均要操作目标值X=10,A将目标值X取回自己工作内存中,开始工作,B在A工作期间,将X修改为’998’,又在A完成工作前,把X改回’10’,等A完成工作,把自己工作内存中的X和内存中当前的X进行比较,结果一致,则误以为自己工作期间,无人操作过X。此上即为ABA问题,
PS:感谢尚硅谷周阳老师