简介
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。
AtomicInteger atomicInteger = new AtomicInteger(10);
System.out.println(atomicInteger.compareAndSet(10, 4));
System.out.println(atomicInteger.compareAndSet(10, 2));
System.out.println(atomicInteger);
底层原理
以AtomicInteger的getAndIncrement方法为例:
public final int getAndIncrement() {
//this:当前对象
//valueOffset:内存地址偏移量(内存地址)
return unsafe.getAndAddInt(this, valueOffset, 1);
}
这里由unsafe调用getAndAddInt方法。
Unsafe
Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法类访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为java中CAS操作的执行依赖于Unsafe类的方法。
Unsafe类中的方法都可以直接调用操作系统底层资源执行相应任务。
变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存便宜地址获取数据的
变量用value修饰,保证了多线程之间的内存可见性。
CAS(Compare and swap),是一条CPU并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
原语的执行必须是连续的,执行过程中不允许打断,所以CAS是一条CPU的原子指令,所以不会造成所谓的数据不一致问题。
var1:AtomicInteger对象本身
var2:该对象值的引用地址
var4:需要变动的值
var5:是用过var1 var2找出的主内存中真实的值
compareAndSwapInt:var2和var5比较,相同则 var5+var4,并返回true,跳出do-while循环。如果不同,继续取值再比较,直至更新完成。
CAS缺点
1)如果CAS失败,会一直尝试,如果CAS长时间不成功,可能会给CPU带来很大的开销
2)只能保证一个共享变量的原子操作
3)ABA问题
ABA
如果一个线程1从内存位置V中取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作将值编程了B,然后线程2又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程1操作成功。
尽管线程1的CAS操作成功,但是不代表这个过程就是没有问题的。
原子引用
AtomicReference:原子引用
//toString,Getter,Setter省略
class Stu{
private String name;
public Stu(String name) {
this.name = name;
}
}
public class AtomicReferenceDemo {
public static void main(String[] args) {
Stu zs = new Stu("zs");
Stu ls = new Stu("ls");
AtomicReference<Stu> reference = new AtomicReference<>(zs);
System.out.println(reference.compareAndSet(zs,ls));
System.out.println(reference.compareAndSet(zs,ls));
}
}
ABA问题解决
新增一种机制,修改版本号(类似时间戳)
存在ABA问题的代码:
new Thread(()->{
atomicReference.compareAndSet(10,11);
atomicReference.compareAndSet(11,10);
},"t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(10,11));
},"t2").start();
使用AtomicStampedReference解决:
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(10,1);
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("t3 first stamp :"+stamp);
atomicStampedReference.compareAndSet(10,11,1,2);
atomicStampedReference.compareAndSet(11,10,2,3);
},"t3").start();
new Thread(()->{
try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(atomicStampedReference.compareAndSet(10, 11, 1, 2));
},"t4").start();
}