CAS全称为compare-and-swap,它是一条CPU并发原语,它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
CAS并发原语体现在java语言中的是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,jvm会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被终端,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致的问题
Unsafe类
Unsafe类是CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地方法(native)来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在rt.jar包中的sun.misc包中,其内部方法可以像C的指针一样直接操作内存,因为CAS操作的执行依赖于Unsafe类的方法。Unsafe类中的底层方法都是native修饰,直接调用操作系统的底层资源执行相应的操作。
valueOffset是当前对象的内存地址偏移量
所有原子类包装最终底层都是通过Unsafe类方法实现。
public class CasTest {
public static void main(String[] args) {
AtomicInteger aInteger = new AtomicInteger(10);
//保存从主内存中拿到的初始值
int expectValue = aInteger.get();
//在设值之前先和原先的值比较,如果是一样的,那么就设值,如果不一样就不设置
boolean flag = aInteger.compareAndSet(expectValue, 2000);
if(flag) {
System.out.println(aInteger.get());
}
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
原子类的其他方法最终是通过compareAndSwap执行。如AtomicInteger的getAndIncrement方法
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
底层是通过getAndAddInt方法实现比较并交换,而getAndAddInt则是调用compareAndSwapInt实现。
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
var1:AtomicInteger对象本身
var2 :该对象值的引用地址
var4:需要变动的数值
var5:通过var1和var2找出主内存中的真实值
用当前对象的值与var5比较,如果相同,更新var5+var4,并返回true
如果不同,继续取值然后比较,直到更新完成。
CAS缺点:
底层是通过循环实现,可能会长时间循环,cpu开销大
只能保证一个共享变量的原子操作
引出ABA问题。
ABA问题
CAS算法实现一个重要前提是需要去除内存中某时刻的数据并在当下时刻比较并替换,那么在 这个时间差之内,可能会导致数据的变化。比如一个线程1从内存位置V处取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作将值编程了B,然后线程2又将内存V位置出的数据修改为A,这时线程1进行CAS操作发现内存中任然是A,然后线程1操作成功。尽管线程1的CAS操作成功,但是并不代表这个过程是没有问题的。
public class ABATest {
private static AtomicInteger atomicReference = new AtomicInteger(100);
public static void main(String[] args) {
new Thread(
()->{
//首次修改
atomicReference.compareAndSet(100,101);
//第二次又降其修改为之前的值
atomicReference.compareAndSet(101,100);
}
).start();
new Thread(
()->{
try {
//线程2睡1秒保证线程1执行完
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程2执行赋值操作,实际上线程1已经修改了相应的值
atomicReference.compareAndSet(100,102);
System.out.println(atomicReference.get());
}
).start();
}
}
原子引用
如果需要将其他类包装成原子类,可以使用AtomicReference实现
class User{
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
}
public class AtomicReferenceTest
{
public static void main(String[] args) {
User u1 = new User("zhangsan",20);
User u2 = new User("lisi",30);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(u1);
//比较并交换,原子操作
atomicReference.compareAndSet(u1,u2);
}
}
解决ABA问题(原子引用+时间戳)
对应的类则是AtomicStampedReference
public class ABAResolve {
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
new Thread(
()->{
int timeStamp = atomicStampedReference.getStamp();
try {
//线程1睡1秒保证两个线程都获取到原始的时间戳
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//首次修改
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
//第二次又降其修改为之前的值,但是时间戳增加1
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
}
).start();
new Thread(
()->{
int timeStamp = atomicStampedReference.getStamp();
try {
//线程2睡3秒保证线程1执行完
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程2执行赋值操作,实际上线程1已经修改了相应的值
System.out.println(atomicStampedReference.compareAndSet(100,102,timeStamp,timeStamp+1)+"\t"+atomicStampedReference.getReference());
}
).start();
}
}