【JavaEE】——CAS指令和ABA问题
阿华代码,不是逆风,就是我疯
你们的点赞收藏是我前进最大的动力!!
希望本文内容能够帮助到你!!
目录
一:CAS指令
1:概念
2:伪代码例子说明
3:优点
二:原子类
1:引入
2:代码示例
3:与volatile的区别
4:标准库源码
三:CAS是如何避免线程安全问题
四:CAS中ABA问题
1:引入
2:极端情况
3:解决方案
一:CAS指令
1:概念
CAS是CPU中的一条特殊的指令,它的功能就是完成“比较和交换”
2:伪代码例子说明
伪代码:只能表示一种逻辑,并不能实现编译执行
注:CAS指令一般只关注内存当中的值,寄存器当中的值是多少不打紧,用完就不要了
3:优点
CAS指令不涉及锁,也能保证线程的安全
二:原子类
1:引入
在Java中:
先是操作系统对指令封装成api
然后JVM在对api进行封装,把CAS的api放到了unsafe这个包里(注:这个包里指令会涉及一些系统底层的内容,使用的话是风险操作)
Java标准库中,对CAS再进一步封装,提供了一些工具类,其中最主要的一个工具叫“原子类”
java.util.concurrent.atomic
2:代码示例
我们还是沿用【JavaEE】——线程的安全问题和解决方式_java线程安全 线程不安全-CSDN博客
这篇文章中,用两个线程去自增到10000计数器这个例子
前面我们是通过synchronized加锁方式解决,这里我们使用原子类进行代码编译
package thread;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Hua YY
* Date: 2024-09-29
* Time: 14:15
*/
public class ThreadDemon36 {
// private static int result = 0;
//括号里的参数就是result的初始值
private static AtomicInteger result = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for(int i = 1 ; i <= 5000 ; i++){
result.getAndIncrement();//自增相当于result++
//result++;
}
});
Thread t2 = new Thread(() -> {
for(int i = 1 ; i <= 5000 ; i++){
result.getAndIncrement();//自增
//result++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(result.get());//获取到result中持有的值
}
}
这里的++操作,不在是(load,add,save)三条指令了,而是打包成了CAS指令,成为一个天然的原子性指令,这样就避免了,多线程中两者的指令相互穿插执行,也就避免了线程安全问题
(1)方法总结
①.getAndIncrement()——相当于count++
②.incrementAndGet()——相当于++count
3:与volatile的区别
volatile是禁止指令重排序(因为操作非原子性嘛),
4:标准库源码
三:CAS是如何避免线程安全问题
核心点:就是通过CAS和while循环的搭配,来确保内存中的值和寄存器当中的值是一样的,
这里的代价就是“自旋”——while循环嘛,但是一般循环不了几次就OK了,这点资源损耗可以忽略
不计,CAS还是很香的~~~
四:CAS中ABA问题
1:引入
上述图文看明白之后,我们可以总结出一点,CAS判断内存和寄存器中的值是否相等,本质上就是在判断——是否有其他线程穿插指令
想象一下,在CAS之前,如果有一个线程穿插进去把数值修改了,紧接着第二个线程也穿插进去把错误的数值又修改回来了,那么CAS是感知不到有线程穿插进来的(如穿~~)
一般来说这也不会引起什么bug之类的
2:极端情况
存钱
3:解决方案
(1)约定数据变化本身就是单向的,只能增加或者只能减少
(2)对于本身必须就是双向变化的数据,引入一个版本号——这个版本号就是只能增加不能减少