JMM之volatile的底层实现原理详解
# JMM之volatile的底层实现原理详解
# 一、简介
从JDK 5开始,Java使用新的JSR-133
内存模型,即JMM,提供了happens-before
原则,其中有一条就是volatile规则,volatile变量的写,先发生于读,这保证了volatile变量的可见性。简单的理解就是,volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能够看到该变量的最新值,
# 二、volatile的作用
# 1、volatile关键字保证可见性
当对volatile变量执行写操作后,JMM会把工作内存中的最新变量值强制刷新到主内存写操作会导致其他线程中的缓存无效
# 2、volatile禁止重排优化,volatile关键字来保证一定的“有序性”
volatile是通过编译器在生成字节码时,在指令序列中添加“内存屏障”来禁止指令重排序的
硬件层的内存屏障
- lfence,是一种Load Barrier 读屏障
- sfence, 是一种Store Barrier 写屏障
- mfence, 是一种全能型的屏障,具备ifence和sfence的能力
- Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁
JVM内存屏障
示例:非常典型的禁止重排优化的例子DCL
# 三、volatile的底层实现原理
# 1、Java层面
volatile修饰的Java变量,保证可见性和有序性
# 2、字节码层面
通过javap -v Test.class
反编译查看字节码文件发现:
volatile在字节码层面,就是使用访问标志:ACC_VOLATILE
来表示,供后续操作此变量时判断访问标志是否为ACC_VOLATILE
,来决定是否遵循volatile的语义处理
# 3、JVM源码层面
当对变量进行操作时,会判断is_volatile
,如果是的话,会调用JVM层的内存屏障storeLoad
。
# 4、汇编语言层面
JVM层的内存屏障storeLoad使用到了lock前缀指令,代码lock; addl $0,0(%%rsp)
其中的addl $0,0(%%rsp)
是把寄存器的值加0,相当于一个空操作(之所以用它,不用空操作专用指令nop,是因为lock前缀不允许配合nop指令使用)
lock前缀,会保证某个处理器对共享内存(一般是缓存行cacheline,这里记住缓存行概念,后续重点介绍)的独占使用。它将本处理器缓存写入内存,该写入操作会引起其他处理器或内核对应的缓存失效。通过独占内存、使其他处理器缓存失效,达到了“指令重排序无法越过内存屏障”的作用(MESI)