volatile
volatile是对共享变量的修饰词,无法修饰局部变量。它保证了被修饰变量的可见性和禁止指令重排。
一、JMM和volatile
JMM里volatile的规则:每个对变量 x 的读都能看到一个对 x 的写。所有对 volatile 变量的读写都是 volatile 动作。
该规则显示声明了volatile对可见性的保证,及暗含了禁止重排序的约定。
二、volatile在各层实现机制
内存栅栏保证了可见性和禁止重排序,禁止编译器、解释器、JIT、CPU进行指令重排且保证可见性。
1. Java语言
1)实现方式:用volatile修饰共享变量
2)主要作用:标识可见性和有序性
2. Java编译器
对volatile修饰的变量在java字节码中增加ACC_VOLATILE的flag,且不会该变量代码进行重排序;
3. 字节码级别
1)实现方式:ACC_VOLATILE修饰变量
2)主要作用:提示JVM解释器和JIT增加内存栅栏,并禁止它们对volatile相关指令重排序;
4. JVM解释器和JIT
使用JVM的OrderAccess方法在volatile相关指令位置增加内存栅栏,且不会该变量代码进行重排序;
1 | // linux_x86下fence方法 |
参考:*addl $0,0(%%esp)*表示将数值0加到esp寄存器中,而该寄存器指向栈顶的内存单元。加上一个0,esp寄存器的数值依然不变。即这是一条无用的汇编指令。在此利用这条无价值的汇编指令来配合lock指令,在__asm__,__volatile__,memory
的作用下,用作cpu的内存屏障。
5. CPU级别
对于 Intel486 和 Pentium 处理器来说,在进行加锁操作时,总是在总线上发出 LOCK#信号,即使锁定的内存区域已经高速缓存在处理器中。
对于 Pentium 4、Intel Xeon、P6 系列处理器而言,加锁操作期间,如果锁定的 内存区域已经被高速缓存进正在执行回写内存加锁操作的处理器中,并且已经完全进 入了高速缓冲线了,则处理器不对总线发出 LOCK#信号。相反,它将修改缓存区域中的 数据,并依赖高速缓存相干性机制来保证加锁操作的执行是原子的。这个操作称为“高 速缓存加锁”。高速缓存相干性机制会自动阻止两个或多个缓存了同一内存区域的处 理器同时修改该区域的数据。
来源:IA-32 架构软件开发人员手册 第 3 卷:系统编程指南 7.1.4
I/O指令、加锁指令、LOCK前缀以及串行化指令等,强制在处理器上进行较强的排序。
来源:IA-32 架构软件开发人员手册 第 3 卷:系统编程指南 7.2.4
简单来说现代CPU会做出如下担保:
1)通过嗅探机制,缓存一致性协议或锁总线保证LOCK修饰指令的可见性;
2)保证LOCK修饰指令在CPU级别有序;
三、Unsafe实现volatile
Unsafe提供了内存栅栏的方法,如下:
1 | public final class Unsafe { |
与volatile的区别
1)Unsafe可自主决定栅栏的位置,较volatile灵活。例如,可以跨方法;有点类似synchronize和Lock的区别;