一:volatile修饰的变量有两大特点
1:保证可见性
2:保证有序性
3:不保证原子性
二:volatile保证可见性
写操作时加入屏障,强制将线程私有工作内存的数据刷回主物理内存
读操作时加入屏障,线程私有工作内存的数据失效,重新回到主物理内存中获取最新值
三:volatile保证有序性
- 存在数据依赖关系,禁止重排序,
- 如果两个操作访问同一变量,而且这两个操作中有一个是写操作,此时两个操作之间就存在数据依赖性
四:volatile不具有原子性
数据读取到本地内存到写回主内存中有三个步骤:数据加载---->数据计算---->数据赋值,如果第二个线程在第一个线程读取旧值和写回新值期间读取i的域值,第一个线程刷回主内存后,第二个线程会作废自己的操作,之前进行的一次i++的操作将会无效,也就造成了线程安全问题。
面试回答为什么不具备原子性:
举例i++的例子,在字节码文件中,i++分为三部,间隙期间不同步非原子操作
五:volatile凭什么可以保证可见性和有序性?
内存屏障
六:内存屏障是什么?
内存屏障其实就是一种JVM指令,它使得CPU或编译器对屏障指令的前和后所发出的内存操作执行一个排序的约束
七:内存屏障能干吗?
阻止屏障两边的指令重排序
八:内存屏障分类
- 粗分为两种
读屏障(Load Barrier):在读指令之前插入读屏障,让工作内存或CPU高速缓存 当中的缓存数据失效,重新回到主内存中获取最新数据。
写屏障(Store Barrier):在写指令之后插入写屏障,强制把缓冲区的数据刷回到主内存中。
- 细分为四种
九:内存屏障插入策略分为4种规则
读屏障:在每个volatile读操作的后面插入一个LoadLoad屏障或者LoadStore屏障
写屏障:在每个volatile写操作的前面插入StoreStore屏障;在每个volatile写操作的后面插入StoreLoad屏障
十:volatile使用场景有哪些?
-
单一赋值可以,但是复合运算赋值不可以(i++之类的)
volatile int a = 10; volatile boolean flag = true;
-
状态标志,判断业务是否结束
-
当读多于写,结合使用内部锁和volatile变量来减少同步的开销
十一:小结
volatile写之前的操作,都禁止重排序到volatile之后
volatile读之后的操作,都禁止重排序到volatile之前
volatile写之后volatile读,禁止重排序