一、基本概念
什么是CAS?
CAS是Compare And Swap比较并交换的简称,主要发生在java的JUC的原子类操作的底层实现中;
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和要修改的新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。
简单验证Demo
package com.jiguiquan.www; import java.util.concurrent.atomic.AtomicInteger; /** * 1、什么是CAS? * 比较并交换(Compare And Swap) * @author jiguiquan * */ public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(3); boolean flag1 = atomicInteger.compareAndSet(3, 666); //3位期望值,666位目标值 System.out.println(flag1+"\t current data: "+atomicInteger); boolean flag2 = atomicInteger.compareAndSet(3, 888); System.out.println(flag2+"\t current data: "+atomicInteger); } }
运行结果如下:
很显然,第一次由于期望值和内存位置当前值一样,都为3,那么可执行修改,将值修改为666;
但是第二次,由于此时内存位置的值已经被修改为666了,与它所期望的值3不一致,那么修改不会被执行,所以值依旧保持为666;
由此,很轻易地验证了CAS机制;
二、CAS的底层原理(难点)
核心两点:
-
Unsafe类
-
自旋锁
1、谈谈对Unsafe类的理解:
JUC中的原子类之所以能够保证原子性,就是依靠的底层的Unsafe类;
这里我们借助 AtomicInteger.getAndIncrement() 方法去追踪它的源码:
getAndIncrement()方法:
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
AtomicInteger原子类中使用的核心变量:
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // 这里引入了Unsafe类 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); } } //此处的value值使用了volatile修饰符,确保了每一次的操作后的value值对其他线程可见 private volatile int value; ... }
Unsafe中的getAndAddInt()方法
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt) { int i; do { i = getIntVolatile(paramObject, paramLong); } while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt)); //compareAndSwapInt是一个本地方法,该方法的实现位于unsafe.cpp中,即使C++代码,其中使用了CPU原语汇编,确保了原子性; return i; }
Unsafe类:
1.1、Unsafe类是CAS的核心类,由于Java无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。
Unsafe类存在于 rt.jar 包下的 sum.misc 包中,其累不操作可以像C的指针一样直接操作内存,因为Java中的CAS操作的执行依赖于Unsafe类的方法;
做个比喻:相当于,“我即使不知道教室XXX的名字,我可以直接用位置描述,第三排第五位同学站起来报一下你的年龄;”——直接用位置来操作
注意:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务;
1.2、变量valueoffset,表示该变量在内存中的偏移量(即在内存中的地址位置),因为Unsafe类就是根据这个内存偏移量来直接读取数据的,非常精确,所以这个值是必须的;
(因为已经跨语言了,总不能把java中的对象传递给C/C++,它们也不认识,所以只能是通过内存中的偏移量(位置)来传递需要操作的对象)
1.3、变量value使用volatile修饰符修饰,确保了在多线程之间的内存可见性;
梳理一下整个过程的执行流程: 假设线程A和线程B两个线程同时执行getAndAddInt操作(直接点,分别跑在不同的CPU上面): 1、AtomicInteger里面的value原始值为3,即主内存中的AtomicInteger的value为3,根据JMM内存模型,线程A和线程B各自持有一份值为3的value的副本到自己的工作内存; 2、线程A通过getIntVolatile(var1,var2)拿到了value值为3,此时线程A被挂起; 3、线程B通过getIntVolatile(var1,var2)拿到了value值为3,但是线程B没有被挂起,并顺利执行了compareAndSwapInt方法,比较内存值也为3,成功修改内存值到4,线程B打完收工,一切OK; 4、这时线程A恢复,执行compareAndSwapInt方法时发现,自己手里的预期值3和此时主内存中的4不一致,说明该值已经被其他线程抢先一步修改过了,那么A线程本次修改失败,只能重新读取,重新再来一遍; 5、线程A重新获取value值,因为变量value被volatile修饰,所以B线程对于value的修改,A线程是能看到的,线程A继续执行compareAndSwapInt进行比较替换,直到成功; |
2、自旋;
AtomicIntger能够实现原子性的底层原理就是:Unsafe类+CAS思想;
比较并交换,直到交换成功,这种操作就被称为自旋,关于自旋锁,后面的章节会详细讲解;
经过了上面的解释,这里再次对CAS做一个总结:
CAS全称为 Compare-And-Swap,它是一条CPU并发原语;
它的功能是判断内存中某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的;
CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮助我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。
再次强调,由于CAS是系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用语完成某个功能的一个过程;并且,原语的执行必须是连续的,在执行过程中不允许被打断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题;也不会被其他线程加塞(必定线程安全)(好比如:习大大出行,5部车出门,中间肯定是不允许被其他车加塞的,根本不用我们担心);
三、CAS的缺点
优点:没有使用synchronized锁,即保证了原子性,又保证了并发性,是一个比较折中的办法,synchronized虽然简单,但是牺牲了并发性;
1、循环时间长,开销大
因为CAS比较交换操作,使用了do…while…循环,只有在成功后才跳出循环,如果不成功则继续循环,如果CAS长时间一直不成功,可能会给CPU带来很大的开销(比如线程数很多,每次比较都是失败,就会一直循环);
2、只能保证一个共享变量的原子操作;
对于多个共享变量的操作,循环CAS就无法保证操作的原子性了,只能考虑使用加锁的方式来保证一组、一段代码的原子性;
3、引出了新的ABA问题(狸猫换太子);
ABA问题在下一篇介绍!