之前在java内存模型中提到过volatile,这次更深入一些探究一些volatile到底是怎么实现有序性.
我们分成五个部分来看
- java源码
- ByteCode字节码
- JVM虚拟机规范
- JVM虚拟机实现
- CPU级别
字节码
源码中使用的volatile关键字在字节码中会被添加一个ACC_VOLATILE的前缀标识
public class VolatileT {
volatile int i;
}
使用 javap -verbose VolatileT.class
指令进行反编译
volatile int i;
descriptor: I
flags: ACC_VOLATILE
看到,在i的flags中添加了ACC_VOLATILE
这个标签,并没有多余的操作
JVM
JVM虚拟机规范规定了四种内存屏障
- LoadLoad屏障: 在后续load前,保证之前load要读取的数据读取完毕
- StroeStroe屏障: 在后续store前, 保证之前要store的数据存储完毕
- LoadStore屏障: 在后续stroe钱,保证之前load要读取的数据读取完毕
- StroeLoad屏障: 在后续load钱, 保证之前要stroe存储完毕
如果两条指令想要操作同一块内存的话,加一层屏障,不允许这两条指令之间进行重排.
volatile在写操作的时候,要求前面指令的写完之后再写,后面的指令等待写完之后再读.
volatile在读操作的时候,要求前面的指令读完之后再读,后面的指令等待读完之后再写.
这是JVM规范,具体要看不同虚拟机的实现.
HotSpot实现内存屏障实际使用汇编语言的Lock指令实现的.
CPU
要想实现在一个CPU中修改完,另一个CPU立刻可以感知到,这需要使用缓存一致性协议.
X86CPU内存屏障
- sfence: 在sfence指令前的写操作必须在sfence指令后的写操作前完成
- lfence: 在lfence指令前的读操作必须在lfence指令后的读操作前完成
- mfence: 在mfence指令前的读写操作必须在mfence指令后的读写操作前完成
fence: 栅栏
s: save
l: load
m: memory