原子性
违反原子性示例
public class Counter{
volatile int i=0;
public void add(){
i++;
}
public static void main (String[]args)throws InterruptedExcetion{
final Counter ct=new Counter();
for(int i=0;i<6;i++){
new Thread(new Runnable(){
public void run(){
for(int j=0;j<10000;j++){
ct.add();
}
}
});
}
Thread.sleep(6000L);
System.out.println(ct.i);
}
}
上面代码是六个线程,每个线程执行一万遍i++;结果会小于6万,出现这种用的原因是:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8KaOzvaW-1642326593364)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211215220044046.png)]
如上图所示,t1和t2进行i++操作,i++通过javap编译后看出经过getfileld(从主内存获取变量值)、iconst_1(入栈)、iadd、putfield(同步到主内存)等操作,t1和t2从主内存第一次获取i的初始值都是0,假如t1线程优先被执行,经过i++操作,i变成1写入到主内存,此时t2工作内存中的i还是之前的初始值0,经过i++后,t2将i=1同步到主内存,此时t2执行的i++相当于失效,所有i++6万次,可能有很多这样的失效操作,导致最后结果小于6万
解决方案
- 可以加入同步锁synchronized、Lock,这样的缺点因为是同步的,同时只支持一个并发
- 使用CAS实现的Automic工具类
CAS
概念
Compare and swap 比较和交换。属于硬件同步原语,处理器提供了基本内存操作的原子性保证。CAS操作需要输入两个数值,一个旧值A(期望操作前的值)和一个新值B,在操作期间先对旧值进行比较,若没有发生变化,才交换成新值,发生了变化则不交换。
CAS解决i++问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o4IyfYY1-1642326593367)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211215225551532.png)]
违反原子性示例的问题通过CAS操作进行解决:
线程进行相加操作,同步到主内存前没有进行CAS 对比操作,比如修改i的变量值同步到主内存前会将修改前的值和主内存中i的值进行对比,如果相等则将修改后的值1同步到主内存,这是t2将相加后的的值1(0,1)同步到主内存,这时经过对比t2线程的i旧值0和主内存中i的值1不相等,所以同步失败,此时t2会进入自悬状态,重新从主内存中加载 i 的值,然后i++操作,然后进行对比操作,到同步到主内存成功为止
CAS的三个问题
- 循环+CAS,自旋的实现让所有线程都处于高频运行(runable状态),争抢CPU执行时间的状态。如果操作长时间不成功,会带来很大的CPU资源浪费
- 仅针对单个变量的操作,不能用于多个变量来实现原子操作
- ABA问题
ABA问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-04jPepVY-1642326593368)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20211219122457972.png)]
如上图所示:
thread1、thread2同时读取到i=0后;thread1、thread2都要执行CAS(0,1)操作;假设thread2操作稍后于thread1,
则thread1执行成功;thread1紧接着执行了CAS(1,0),将i的值改回0,这样thread2执行CAS(0,1)预期是失败的,但是可以执行成功。
该问题可以通过版本号来解决,如使用AtomicStampedReference类
原子操作封装类
- AtomicBoolean :原子更新布尔类型
- AtomicInteger: 原子更新整型
- AtomicLong: 原子更新长整型
- AtomicIntegerArray: 原子更新整型数组里的元素
- AtomicLongArray: 原子更新长整型数组里的元素
- AtomicReferenceArray: 原子更新引用类型数组里的元素
- AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。
- AtomicStampedReference: 原子更新带有版本号的引用类型。
- AtomicStampedReference: 原子更新带有版本号的引用类型**(有效防止ABA问题)**
1.8更新
计数器增强版,高并发下性能更好
更新器: DoulbeAccumulator、LongAccumulator
计数器: DoubleAdder、LongAdder
原理: 分成多个操作单元,不同线程更新不同的单元,只需要汇总的时候才计算所有的单元操作。
场景: 高并发频繁更新、不太频繁地读取。
导致线程安全的原因
- 可见性
- 安全性
分成多个操作单元,不同线程更新不同的单元,只需要汇总的时候才计算所有的单元操作。
场景: 高并发频繁更新、不太频繁地读取。
导致线程安全的原因
- 可见性
- 安全性