volatile是面试中常常被问到的,现在来总结一下volatile的关键地方。
volatile是如何实现禁止指令重排的???
先来说一下什么是指令重排:
int m = 1;
int n = 1;
JVM中的顺序可能是先执行初始化n,再初始化m,这个就叫做指令重排。
为何JVM要进行指令重排?
指令重排就初衷就是在代码不改变执行的结果的情况下改变代码执行的顺序,这个执行层面是操作系统级别的,不是简单的JVM,可以理解为是CPU操作JVM字节码的时候的一个操作。
指令重排是带来什么??
单线程情况下不是很明显,多线程情况下必然会产生一定的问题。
volatile和cpu指令重排有什么关系?
被volatile修饰的变量会禁止指令重排,中间有个内存屏障使屏障前后的指令不会发生顺序的变化。
volatile如何做到禁止指令重排的?
内存屏障:内存屏障称为内存栅栏或栅栏指令是一种屏障指令,它使cpu或编译器对屏障指令之前的之后发出的内存操作执行一个排序约束。这通常意味着在屏障之前发布的操作被保证在屏障之后发布的操作之前执行。
内存屏障的种类???
一共有四种:
(1)LoadLoad屏障
抽象场景:Load1;LoadLoad;Load2
Load1和Load2代表两条读取指令。在Load2要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
(2)StoreStore屏障
抽象场景:Store1;StoreStore;Store2
Store1和Store2代表两条写入指令。在Store2写入执行前,保证Store1的写入操作对其它处理器可见。
(3)LoadStore屏障
抽象场景:Load1;LoadStore;Store2
在Store2被写入前保证Load1要读取的数据被读取完毕。
(4)StoreLoad屏障
抽象场景:Store1; StoreLoad; Load2
Load2读取操作执行前,保证Store1的写入对所有处理器可见。StoreLoad屏障的开销是四种屏障中最大的。
当一个变量被volatile修饰之后从JVM层面会对这个变量做两件事情:
<1>每个volatile写操作前插入StoreStore屏障,写操作后插入StoreLoad屏障
<2>每个volatile读操作前插入LoadLoad屏障,读操作后插入LoadStore屏障
因此可以证明volatile除可见性外的另一个特性:阻止编译时和运行时的指令重排,编译时JVM编译器遵循内存屏障的约束,运行时候依靠cpu屏障来阻止重排。