一:valotile只能保证可见性和有序性,不能保证原子性
1:可见性
1):可见性:是在被valotile修饰的变量,被修改后,强制将被修改的变量同步到系统内存中,而其他cpu(即其他线程)在读取自己内部缓存中(也叫工作内存,私有的数据工作栈)的值的时候,发现是被valotile修饰的,会将内部缓存的值设为无效,然后从系统内存中读取。(后面2),3)是帮助理解)
2):举例说明(不可见性):
①:线程A在工作内存中(私有的数据工作栈)更新了变量,还没同步到主内存(系统内存)中,而此时线程B只能获取到还未更新的旧变量值;
②:线程A成功的将变量更新到主内存中,单线程B依旧使用的是自己工作内存中的旧变量值,并没有去主内存中获取新的变量值
解决方案:
对于上面的不可见性,使用valotile修饰的变量会达到的效果是:1. 强制立即同步到主内存中,2.强制立即失效工作内存中变量值,这就使得线程B必须去主内存中获取最新的变量值,从而达到线程A的变量修改对线程B是可见的
3):总结:造成数据不可见性的原因是:cpu不是直接和系统内存通信的,而是把变量读取到工作内存中(内部缓存),修改也是在工作内存中,但是 将工作内存的修改同步到系统内存中,是不确定的,有了时间差,从而导致读取到的值,不是最新值
2:有序性
1):有序性:有序性是通过内存屏障来实现的,内存屏障可以理解为在某些指令中插入屏障指令,用以确保,在向屏障指令后面执行的时候,其前面的所有指令已经执行完毕;
2):案例
在写单例模式时,我们通常会采用双层判断的方式,在最内层:
instance = new Singleton()
其实这也有一个隐含的问题:这句赋值语句,其实是分三步来操作的:
a.为instance分配内存
b.调用Singleto构造函数来初始化变量
c.instance指向上一步初始化的对象
在jvm做了指令重排序优化后,上述步骤b和c不能保证,可能出现,c先执行,但是对象却没初始化,这时候其他线程判断的时候,发现是非null,但是使用的时候,却没有具体实例,导致报错。
所以,我们可以用valotile来修饰instance,避免该问题。
3):总结:造成指令无序的原因:虚拟机在把代码编译为指令后执行,出于优化的目的,在保证结果不变的情况下,可能会调整指令 执行顺序
3:原子性(不能保证)
最简单的例子就是,i++,在多线程环境下,最终的结果是不确定的,为什么?就是因为这么一个++操作,被编译为指令 后,是多个指令来完成的。那么遇到并发的情况,就会导致彼此“覆盖”的情况
原子操作解释可以参考:
4:注意事项
1):注意:volatile 修饰 数组/Object 时,只有在数组/Object的变量引用发生变化时才内存可见,而数组/Object中的元素/属性被改变时没有内存可见性
例如:volatile int[] a={1,2,3 } ; a[1]=5;//无可见性 a={6,7,8};//有可见性 ;实际上 volatile只是关注变量a的变化,而不关心其引用的数组的变化