Java中CAS操作是怎么保证原子性的?
在学习Java多线程时我们会接触到CAS这样一个概念,CAS其实就是Compare And Swap的一个缩写。Compare And Swap,顾名思义就是比较并交换,其实就是把当前值与你预期的值进行一个比较,如果一样再进行修改,否则不修改并返回失败。这里我解释一下,当前值与预期值的一个概念:多线程环境下很可能发生当我拿到一个值还没修改的时候,别的线程已经把这个值修改掉了,这样就会引发线程安全问题,所以CAS操作就是在修改前用一个当前值和期望值来进行比较,只有相等,即没有其他线程修改过这个值的时候(也可能发生一个线程改了,又有一个线程又改回来的情况,CAS无法感知到这种操作,它还是会认为数据没有发生变化而进行修改。这种情况可以利用AtomicStampedReference和AtomicMarkableReference解决,两者根据应用场景不同一定区别,这里不做阐述),才进行修改,从而保证操作的原子性。
可以看到compareAndSet方法中的expect(期望值),update(修改的值)。
CAS本身操作的原子性
那么CAS这个操作本身又是怎么保证原子性的呢?可以想象:比较——修改这是两个动作,可能我比较的时候它是一样的,当我修改的时候它却被别的线程修改了。这就涉及到CAS本身这个操作是原子的,也就是不被其他线程所干扰的。这是利用CPU的原语来实现的。我们知道Java方法无法直接访问底层系统,需要通过本地(Native)方法来访问,Unsafe相当于一个桥梁,基于该类可以连接底层的操作系统直接操作特定的内存数据,Unsafe类存在sun.misc包中,其内部方法操作可以像C指针一样直接操作内存,因此Java中的CAS操作的执行依赖于Unsafe类的方法。(Unsafe类很重要!不知道的同学可以多去了解,建议自己利用反射获取unsafe对象自己写一些东西,比如自己写一个原子类)
进入compareAndSet方法我们发现他返回的是unsafe的compareAndSwapInt方法:
进入Unsafe类的native方法compareAndSwapInt,既然是native方法,那就不涉及到Java的代码了,调用UnSafe类中的CAS方法,也就是这个本地native方法,JVM会帮我们实现出CAS汇编指令,这是一种完全依赖于硬件的功能,通过它实现了原子操作,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致的问题,也就是说CAS是线程安全的。
注意Unsafe类的所有方法都是native修饰的,也就是说unsafe类中的方法都直接调用操作系统底层资源执行相应的任务。
变量valueOffset
表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的,这个变量我们在自己利用unsafe对象写一些操作的时候会用到,其实Compare操作也是一个内存比较的过程。
附上一个自己写的原子类:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
class MyAtomicInteger {
private volatile int value;
private static final long valueOffset;
static final Unsafe UNSAFE;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null); //这个成员变量就是Unsafe类型
UNSAFE = unsafe;
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
}
static {
try {
//获取value的偏移量,用于Unsafe直接访问该属性
valueOffset = UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
} catch (NoSuchFieldException e) {
e.printStackTrace();;
throw new RuntimeException(e);
}
}
public int getValue() {
return value;
}
public void increment(int amount){
while (true){
int prev = this.value;
int next = prev - amount;
//CAS
if (UNSAFE.compareAndSwapInt(this,valueOffset,prev,next)){
break;
}
}
}
}
以上只是个人学习中一些感悟与分享,如有错误欢迎指正与交流。