CAS (Compare And Swap)
概念
CAS的全称是Compare-And-Swap,它是CPU并发原语。比较交换(自旋锁)
它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的
CAS并发原语体现在Java语言中就是sun.misc.Unsafe
类的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令,
这是一种完全依赖于硬件的功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干
条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原
子指令,不会造成所谓的数据不一致的问题,也就是说CAS是线程安全的。-----也即是说CAS是一条原子指令,不会造成所谓的数据不一致
的问题.
底层原理
1、unsafe类-------rt.jar中的源码
AtomicInteger:
// 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;
/**
* Atomically increments by one the current value.
*
* @return the previous value this:当前对象
*/
public final int getAndIncrement() {
// this:当前对象 valueOffset:内存偏移量
return unsafe.getAndAddInt(this, valueOffset, 1);
}
Unsafe:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//从主内存获取最新值 var1 var2用来计算主内存值
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(Native)方法来访问,Unsafe相当于一个后门,基于该类
可以直接操作特定的内存数据。Unsafe类存在sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中的CAS操作
的执行依赖于Unsafe类的方法。
注意Unsafe类的所有方法都是native修饰的,也就是说unsafe类中的方法都直接调用操作系统底层资源执行相应的任务
为什么Atomic修饰的包装类,能够保证原子性,依靠的就是底层的unsafe类
CAS的缺点
- 循环时间长开销很大
- 只能保证一个共享变量的原子性
- 引出来ABA问题
ABA问题
狸猫换太子
CAS会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B然后
线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
造成的问题:
小明账户余额为100元。
并发条件下扣款50元,
有两个线程扣款50,
在线程1扣款完毕后,将余额修改为50元。
这时有人给小明转账50元。线程3将余额修改为100元。
线程1发现余额为100元。以为还没有扣款,于是将余额修改为50元。造成重复扣款。
原子引用类型AtomicReference
原子引用其实和原子包装类是差不多的概念,就是将一个java类,用原子引用类进行包装起来,那么这个类就具备了原子性
原子引用类型的使用:
User zhangSan=new User("1","zhangSan");
// 创建原子引用包装类
AtomicReference<User> atomicReference = new AtomicReference<>();
// 现在主物理内存的共享变量,为zhangSan
atomicReference.set(zhangSan);
解决ABA问题:使用AtomicStampedReference:
用修改的版本号对比来避免ABA问题
/**
* 传递两个值,一个是初始值,一个是初始版本号
*/
static AtomicStampedReference<Integer> atomicStampedReference
= new AtomicStampedReference<>(100, 1);
// 获取版本号
int stamp = atomicStampedReference.getStamp();
// 传入4个值,期望值,更新值,期望版本号,更新版本号
atomicStampedReference.compareAndSet(100, 101,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);