作用
- 可见性
- 有序性
JAVA
先写个Demo
package demo;
public class VolatileDemo {
static volatile int value = 0;
public static void main(String[] args) {
value = 1;
System.out.println(value);
}
}
关键就看对value的读写;
字节码
javap反编译一下
0: iconst_1
1: putstatic #2 // Field value:I
4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
7: getstatic #2 // Field value:I
10: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
很明显putstatic,getstatic实现对static类型的value的读写;
JVM
话不多说直接上源码,先看写的情况:
// hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp
// 是否用volatile修饰
if (cache->is_volatile()) {
// 把值写入内存,是一个store操作
...
// 内存屏障,保证值写入内存之后再执行后面的指令
OrderAccess::storeload();
}
// hotspot/src/os_cpu/linux_x86/vm/orderAccess_linux_x86.inline.hpp
inline void OrderAccess::storeload() { fence(); }
// hotspot/src/os_cpu/linux_x86/vm/orderAccess_linux_x86.inline.hpp
inline void OrderAccess::fence() {
// 只有多核环境下才需要内存屏障
if (os::is_MP()) {
// always use locked addl since mfence is sometimes expensive
#ifdef AMD64
__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
}
}
汇编
__asm__ volatile表示执行内联汇编,并且禁止编译器对内联汇编优化;cc不重要;memory禁止编译器重排序,从软件层面保证了有序性;addl $0,0(%%rsp)把0加到rsp寄存器,就是个空操作,重点就是lock前缀;
硬件
上Intel开发者手册:
Locking operations typically operate like I/O operations in that they wait for all previous instructions to complete and for all buffered writes to drain to memory.
所有之前的指令完成后再把store buffer里的所有写操作刷入内存,cpu的嗅探机制发现store buffer刷新到内存会自动无效化自己的缓存,从硬件层面保证了有序性和可见性;