我是 ABin-阿斌:写一生代码,创一世佳话。 如果小伙伴们觉得我的文章有点 feel ,那就点个赞再走哦。
文章目录
一、CAS简介
- 全称 Compare and swap,字面意思:【比较并交换】
1. CAS有哪些操作:
- 需求场景: 我们假设内存中的原数据 V,旧的预期值 A,需要修改的新值 B。
-
三步操作:
- 比较 A 与 V 是否相等。(比较)
- 如果比较相等,将 B 写入 V。(交换)
- 返回操作是否成功。
2. 注意事项:
- 当多个线程同时对某个资源进行 CAS 操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。可见 CAS 其实是一个乐观锁。
3. 代码演示:
/**
* @description: Compare and swap 简称:CAS 中文:比较并交换
*/
public class CASDemo {
public static void main(String[] args) {
//原子类的类,主要用于在高并发环境下的高效程序处理,来帮助我们简化同步处理
AtomicInteger atomicInteger = new AtomicInteger(2021);
//比较并设置,参数一:期望值 参数二:更新值
boolean update = atomicInteger.compareAndSet(2021, 2022);
if (update) {
System.out.println("修改成功:" + atomicInteger.get());
} else {
System.out.println("修改失败!:" + atomicInteger.get());
}
//以原子方式将当前值递增+1
atomicInteger.getAndIncrement();
boolean flag = atomicInteger.compareAndSet(2021, 2022);
if (flag) {
System.out.println("修改成功:" + atomicInteger.get());
} else {
System.out.println("修改失败!:" + atomicInteger.get());
}
}
}
运行结果:
AtomicInteger 类介绍:
- java.util.concurrent.atomic 的包下的原子类的类,主要用于在高并发环境下的高效程序处理,来帮助我们简化同步处理。
- 不单单这一个类,还有 AtomicBoolean,AtomicInteger,AtomicLong,AtomicLongArray,AtomicReference 等也都是同上。
AtomicInteger 源码解析:
- 对 Volatile 和 Unsafe 不懂的小伙伴可以看我的这篇文章:JMM与Volatile的具体介绍与使用
getAndIncrement() 方法源码分析:
- 从下方源码中我们可看到Java是直接从内存中获取的,所以效率是非常高的
- 同时,下方白色源码当中是 一种锁机制: 自旋锁, 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作。如果不是,那么(do while 无限循环)
4. CAS的缺点
- 性能问题,我们使用时大部分时间使用的是 while true 方式对数据的修改,直到成功为止。
- 优势就是相应极快,但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。
- ABA问题
二、ABA问题
1. 什么是ABA
- 比如 张三现在查询学生表中的语文成绩是96,当张三想要修改这个数据的时候又去查了一遍 还是 96。那么在这个时候我们从直观的角度来看这个数据是没有被别人修改过的,但是它真的没有被修改过吗?
- 其实不然,在张三查询第二遍的这段时间中,这个 96 很有可能被李四改成 69,结果李四一看不对呀,改错了,又慌慌张张的改成 96 了。
- 那么,CAS 就会误认为这个值一直都是 96 从未改过。这是不是就有问题了,所有这个问题我们就统称为:CAS操作的——>ABA问题。
2. 代码演示
/**
* @description: CAS:ABA问题测试
*/
public class ABADemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(96);
// 如果我期望的值达到了,那么就更新,否则,就不更新
System.out.println(atomicInteger.compareAndSet(96,100));
System.out.println(atomicInteger.get());
// ============== 捣乱的李四 ==================
System.out.println(atomicInteger.compareAndSet(100,69));
System.out.println(atomicInteger.get());
// ============== 期望的结果 ==================
System.out.println(atomicInteger.compareAndSet(69,100));
System.out.println(atomicInteger.get());
}
}
结果:
3. 如何避免ABA问题
- 原子引用:(乐观锁思想) AtomicStampedReference 类可以解决ABA问题。这个类维护了一个【版本号】Stamp,其实有点类似乐观锁的意思。在进行 CAS 操作的时候,不仅要比较当前值,还要比较版本号。只有两者都相等,才执行更新操作。
注意:
- AtomicStampedReference<>(6, 1); 这两个参数不要填的太大,因为我们本次测试用的是 Integer 类型
- Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实例,而不是 new,因为 valueOf 使用缓存,而 new一定会创建新的对象分配新的内存空间。
代码演示:
/**
* @description: 解决 CAS 带来的 ABA问题
*/
public class ABADemo2 {
static AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(6, 1);
public static void main(String[] args) {
//使用版本号机制来验证ABA问题
new Thread(() -> {
//获取当前版本号
int stamp = atomic.getStamp();
System.out.println("线程A1的版本号为:" + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomic.compareAndSet(6, 10, atomic.getStamp(), atomic.getStamp() + 1);
System.out.println("线程A2的版本号为:" + atomic.getStamp());
System.out.println(atomic.compareAndSet(10, 6, atomic.getStamp(), atomic.getStamp() + 1));
System.out.println("线程A3的版本号为:" + atomic.getStamp());
}, "线程A:").start();
//使用【乐观锁】思想解决ABA问题
new Thread(() -> {
int stamp = atomic.getStamp();
System.out.println("线程B1的版本号为:" + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomic.compareAndSet(6, 2, stamp, stamp + 1));
System.out.println("线程B2的版本号为:" + atomic.getStamp());
}, "线程B:").start();
}
}
结果展示: