1.CAS
CAS 是指Compare And Swap,比较并交换,是一种很重要的同步思想。如果主内存的值跟期望值一样,那么就进行修改,否则一直重试,直到一致为止。
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2019));
System.out.println("当前值:" + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 100));
System.out.println("当前值:" + atomicInteger.get());
}
}
- 运行结果:第一次修改,期望值为5,主内存也为5,修改成功,为2019。第二次修改,期望值为5,主内存为2019,修改失败。
- 查看AtomicInteger.getAndIncrement()方法,发现其没有加synchronized也实现了同步。这是为什么?
2.CAS底层原理
AtomicInteger内部维护了volatile int value和private static final Unsafe unsafe两个比较重要的参数。
public final int getAndIncrement(){
return unsafe.getAndAddInt(this,valueOffset,1);
}
AtomicInteger.getAndIncrement()调用了Unsafe.getAndAddInt()方法。Unsafe类的大部分方法都是native的,用来像C语言一样从底层操作内存。
public final int getAnddAddInt(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和var2,就是根据对象和偏移量得到在主内存的快照值var5。然后compareAndSwapInt方法通过var1和var2得到当前主内存的实际值。如果这个实际值跟快照值相等,那么就更新主内存的值为var5+var4。如果不等,那么就一直循环,一直获取快照,一直对比,直到实际值和快照值相等为止。(自旋操作)
CAS缺点
CAS实际上是一种自旋锁
- 1.一直循环,开销比较大。
- 2.只能保证一个变量的原子操作,多个变量依然要加锁。
- 3.引出了ABA问题。
ABA问题
所谓ABA问题,就是比较并交换的循环,存在一个时间差,而这个时间差可能带来意想不到的问题。比如线程T1将值从A改为B,然后又从B改为A。线程T2看到的就是A,但是却不知道这个A发生了更改。尽管线程T2 CAS操作成功,但不代表就没有问题。 有的需求,比如CAS,只注重头和尾,只要首尾一致就接受。但是有的需求,还看重过程,中间不能发生任何修改,这就引出了AtomicReference原子引用。
AtomicReference
AtomicInteger对整数进行原子操作,如果是一个POJO呢?可以用AtomicReference来包装这个POJO,使其操作原子化。
User user1 = new User("Jack",25);
User user2 = new User("Lucy",21);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(user1);
System.out.println(atomicReference.compareAndSet(user1,user2)); // true
System.out.println(atomicReference.compareAndSet(user1,user2)); //false
AtomicStampedReference和ABA问题的解决
使用AtomicStampedReference类可以解决ABA问题。这个类维护了一个“版本号”Stamp,在进行CAS操作的时候,不仅要比较当前值,还要比较版本号。只有两者都相等,才执行更新操作。
public class ABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
//第一个参数初始值,第二个参数初始版本号
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
System.out.println("======ABA问题的产生======");
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start();
new Thread(() -> {
try {
//先睡一秒让上面线程先执行交换两次
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2020)
+ "\t" + atomicReference.get().toString());
}, "t2").start();
try{ TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
- 运行结果,值从100->101->100,最后另外线程还是能够从100->2020,说明包装原子类是无法解决ABA问题的。即数据中途被换过无法被发现。
AtomicStampedReference.compareAndSet(expectedReference,newReference,oldStamp,newStamp);
public class ABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
//第一个参数初始值,第二个参数初始版本号
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
System.out.println("======ABA问题的产生======");
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start();
new Thread(() -> {
try {
//先睡一秒让上面线程先执行交换两次
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2020)
+ "\t" + atomicReference.get().toString());
}, "t2").start();
try{ TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("======ABA问题的解决======");
new Thread(() -> {
//初始版本号
int initVersion = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t初始版本号:" + initVersion);
try{ TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
atomicStampedReference.compareAndSet(100, 101,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第二次版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第三次版本号:" + atomicStampedReference.getStamp());
}, "t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号: " + stamp);
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
boolean result=atomicStampedReference.compareAndSet(100,2019,
stamp,stamp+1);
System.out.println(Thread.currentThread().getName()+"\t修改成功与否:"+result+" 当前最新版本号"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t当前实际值:"+atomicStampedReference.getReference());
}, "t4").start();
}
}