开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情
本文主要介绍CAS(比较并交换),以及CAS存在的ABA问题(狸猫换太子)及原子引用来解决ABA问题。
20. CAS、ABA问题及原子引用
什么是CAS
CAS:compareAndSet比较并交换。与期望值进行比较,如果是期望的值,就更新,否则不更新。CAS是CPU的并发原语。
如下述代码,用原子类,设置初始2020,期望2020,如果达到期望2020,就更新成2021。
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//参数1期望,参数2更新
// public final boolean compareAndSet(int expect, int update)
// 如果期望的值达到了就更新,否则就不更新,CAS是CPU的并发原语
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
当然,CAS也有一些缺点:
- 底层循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
什么是ABA问题呢
ABA问题类似于狸猫换太子。比如说有两个线程,初始共享变量是1,其中A线程CAS1期望值是1,更新值是2,CAS2期望值是2,更新值是1;B线程期望值是1,更新值是2,且B做了个sleep,慢一点。针对这种情况,线程A将共享变量从1更新到2,又从2更新到1,但是B全然不知的。
代码举例:
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
// 期望、更新
// public final boolean compareAndSet(int expect, int update)
// 如果我期望的值达到了,那么就更新,否则,就不更新, CAS 是CPU的并发原语!
// ============== 捣乱的线程 ==================
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
// ============== 期望的线程 ==================
System.out.println(atomicInteger.compareAndSet(2020, 6666));
System.out.println(atomicInteger.get());
}
针对这种问题呢,就要用到原子引用了,原子引用对应的思想是乐观锁的思想。
什么是原子引用
简单说:原子引用就是一种带版本号的原子操作。
下面的代码做的事情就是:原子操作加了版本号,更新的时候会更新版本号。线程A先进行了带版本号的CAS,更新原始值为2,更新成功,然后又将2更新成1,此时版本号变成了3(初始版本号1,更新了两次+1)。B线程去做CAS,发现数值正确但版本号不正确,所以更新失败。
这里要注意的几个点:
- integer有个坑,integer使用了对象缓存机制,默认范围是-128~127(如果integer不在这个范围,下面代码的CAS会出现线程B更新成功的问题),推荐使用静态工厂方法valueof获取对象实例,而不是new,因为valueof使用缓存,而new一定会创建新的对象分配新的内存空间。这是阿里巴巴java规范中提到的。
- 所有包装类的比较,全部使用equals方法比较
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("a1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1));
System.out.println("a2=>" + atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1));
System.out.println("a2=>" + atomicStampedReference.getStamp());
},"a").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("b1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));
System.out.println("b2=>" + atomicStampedReference.getStamp());
},"b").start();
}