1. 什么是CAS
CAS的全称为Compare-And-Swap,直译就是对比交换。是一条CPU的并发原语,其作用是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
CAS并发原语体现在java语言中就是sun.misc.Unsafe类中的各种方法。调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过过程中不予许被中断,也就是说CAS是一条CPU的原语指令,不会造成所谓的数据不一致问题。
2.Unsafe类
Unsafe是CAS的核心类,由于java方法无法直接访问底层系统,需要用过本地方法(native)来访问,Unsafe相当于一个后门,基于类可以直接操作特定内存的数据。Unsafe类位于sun.misc包中,其内部方法操作可以像C的指针一样操作内存,因为CAS操作是执行依赖于Unsafe类的方法。
3.AtomicInteger源码
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// rt.jar中的sun.misc.Unsafe
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 内存偏移量,变量在内存中的地址
private static final long valueOffset;
// 操作变量加volatile关键字,使得所有的线程都可以访问到。
private volatile int value;
static {
try {
// 获取到变量在内存中的地址
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
public final int getAndIncrement() {
// this为当前操作的对象,valueOffset 为变量的地址, 1 +1
return unsafe.getAndAddInt(this, valueOffset, 1);
}
}
// rt.jar中sun.misc的Unsafe类
public final class Unsafe {
// 源码请看下图
public final int getAndAddInt(java.lang.Object o, long l, int i) { /* compiled code */ }
}
4.CAS的缺点
- 循环时间长,开销大。如果CAS失败,会一直进行尝试,如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
- 只能保证一个共享变量的原子操作。
- 会导致ABA问题
5.ABA问题
CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程t1从内存位置v中取出A,这个事件另一个线程t2为取出A,并且线程t2进行了一些操作将值改成了B,然后线程t2又将B改成了A,这个时候t1线程进行CAS操作的时候发现内存中的值还是A,然后线程t1操作成功。
public class TestAtomicDemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
System.out.println("t1做ABA数据交换");
}, "t1").start();
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean flag = atomicReference.compareAndSet(100, 1000);
System.out.println("t2\t" + flag + "\t" + atomicReference.get());
}, "t1").start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// ABA 解决方案:加上版本号的校验,类似于数据库的乐观锁
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int stamp = atomicStampedReference.getStamp();
System.out.println("t3版本号:" + stamp);
atomicStampedReference.compareAndSet(100, 101, stamp, stamp + 1);
atomicStampedReference.compareAndSet(101, 100, stamp + 1, stamp + 2);
System.out.println("t3做ABA数据交换, 版本号为:" + atomicStampedReference.getStamp());
}, "t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println("t4版本号:" + stamp);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean flag = atomicStampedReference.compareAndSet(100, 1000, stamp, stamp + 1);
System.out.println("t4\t" + flag + "\t" + atomicStampedReference.getReference());
}, "t4").start();
}
}
结果如下,用AtomicStampedReference这个类,加上版本号,可以避免ABA问题