前言
被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象。
相信很多人都用过volatile这个关键字,也知道它的妙用,但是其底层原理是否知晓呢?通过这篇文章就一目了然了。 在了解volatile之前CPU多及缓存架构和JMM内存模型,如果不了解的在我的其他文章里面有讲到这两点。
volatile能保证原始数据类型赋值的原子性,无法保证复合操作的原子性。
上面程序运行最终结果都会小于等于10000,为什么会出现这种情况呢,罪魁祸首就是在并发情况下违背了原子性操作导致,接下来一步一步进行分析原由。
只需要在add方法处加上synchronized关键字进行修改,保证add方法的原子性就能保证每次执行结果都是10000。synchronized回在后面的文章中讲到
[a=1,b=0]:线程2先执行语句3、4,此时读取从主内存获取到变量x值为0,并为变量b赋值0,之后赋y为1,并将=1写入到主内存,之后线程1执行到语句1,从主内存获取y=1赋值给a
[a=0,b=1]:线程1先执行语句1、2,此时读取从主内存获取到变量y值为0,并为变量a赋值0,之后赋x为1,并将=1写入到主内存,之后线程2执行到语句3,从主内存获取x=1赋值给b
语句1和语句2进行了指令重排,导致语句1优先与语句2先执行,语句3、4同理,此时主内存中x、y都变成了1,之后a、b赋值的时候都取到了1
执行的结果就只有三种情况了[a=0,b=0, a=1,b=0, a=0,b=1]
volatile修饰变量之后回禁止指令重排,语句1一定在语句2之前执行,语句3一定会在语句4之前执行。
上面的例子符合第二个volatile写第一个普通写规则,编译器不会进行重排序。
需要注意的是:volatile写是在前面和后面分别插入内存屏障,而volatile读操作是在后面插入两个内存屏障
StoreStore屏障:禁止上面的普通写和下面的volatile写重排序;
StoreLoad屏障:防止上面的volatile写与下面可能有的volatile读/写重排序
LoadLoad屏障:禁止下面所有的普通读操作和上面的volatile读重排序