(之前被面试官问起多线程CAS相关问题时,亿脸懵逼,赶紧恶补一下。)
1.概念: CAS是compare and swap的缩写,即比较与交换。
就是你修改主内存中的值时先要比对之前的值,如果相同那么就修改。
或者这么理解: 它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
2.CAS底层原理 CAS如何实现?
unsafe.getAndAddInt原理
3.总结CAS:
CAS(CompareAndSwap) 比较当前工作内存种的值和主内存种的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存种的值一致为止。
CAS应用:
CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
4.CAS的缺点
》循环时间长开销很大
》只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作。但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候可以用锁来保证原子性。
》会导致ABA问题
5.谈谈原子类AtomicInteger的ABA问题
CAS算法实现一个重要前提需要取出内存种某时刻的数据并在当下时时刻比较并替换,那么这个时间差异类会导致数据的变化。
比如线程1从内存位置V种取出A,这时另一线程2也从内存种取出A,并且线程2进行了一系列操作将值改为了B,然后线程2又将V位置的数据变成A,这时候线程1进行CAS操作发现内存仍然是A,然后线程1操作成功。
总结:尽管线程1的CAS操作成功,但是不代笔这个过程就是没有问题的。
6.ABA问题的解决:
原子引用 + 新增一种机制,那就是修改版本号(类似时间戳)
存在ABA问题的案例:
/**
* @author: dada
* @date: 2020/12/18
* @description: ABA案例1:存在ABA问题
*/
public class ABADemo {
//默认初始值100
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
public static void main(String[] args) {
//线程t1
new Thread(() -> {
//期望值100,修改为101
atomicReference.compareAndSet(100,101);
//期望值101,修改为100
atomicReference.compareAndSet(101,100);
},"t1").start();
//线程t2
new Thread(() -> {
try {
//休眠1s,保证t1线程完成了一次ABA操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100,2019) + "\t" + atomicReference.get());
},"t2").start();
}
}
输出:
true 2019
改进后:
/**
* @author: dada
* @date: 2020/12/18
* @description: ABA案例1:存在ABA问题
*/
public class ABADemo2 {
//默认初始值100
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
//计数:初始值100,初始计数值1
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
//线程t1
new Thread(() -> {
//期望值100,修改为101
atomicReference.compareAndSet(100,101);
//期望值101,修改为100
atomicReference.compareAndSet(101,100);
},"t1").start();
//线程t2
new Thread(() -> {
try {
//休眠1s,保证t1线程完成了一次ABA操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100,2020) + "\t" + atomicReference.get());
},"t2").start();
try {
//暂停一会线程
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("========解决:ABA问题================");
//线程t3
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t 第一次版本号:" + stamp);
//暂停一会,让t4也获取stamp初始值
try {
//暂停一会线程t3
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//t3模拟ABA操作
// 四个参数:期望值,修改值,版本号,版本号+1
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();
//线程t4
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t 第一次版本号:" + stamp);
try {
//暂停一会线程t4,保证上面t3完成ABA操作
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
//参数:期望值100,新值2020,期望版本号1,版本号+1
//注意:此时的版本号已经被改为3,所以与期望版本不一致,修改失败
boolean result = atomicStampedReference.compareAndSet(100, 2020, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()
+ "\t 修改成功否result:" + result
+ "\t 最新版本号:" + atomicStampedReference.getStamp()
+ "\t 最新值:" + atomicStampedReference.getReference());
},"t4").start();
}
}
输出:
true 2020
========解决:ABA问题================
t3 第一次版本号:1
t4 第一次版本号:1
t3 第二次版本号:2
t3 第三次版本号:3
t4 修改成功否result:false 最新版本号:3 最新值:100
//。。。。。。。。。。。。OVER