Volatile
volatile 是 Java 中的关键字,是一个变量修饰符,被用来修饰会被不同线程访问和修改的变量
语义
一旦一个共享变量被 volatile 修饰之后,就具备两层语义
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这个新值对其他线程来说是立即可见的
- 禁止进行指令重排序
作用
- 原子性
volatile 仅仅保证对任意单个 volatile变量的读/写具有原子性,但类似于volatile++这种复合操作以及多个volatile的读写不具有原子性 - 可见性
对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile 变量最后的写入 - 有序性
对 volatile 修饰的变量的读写操作前后加上各种特定的内存屏障来禁止指令重排序,保证有序性
实现原理
字节码层面
ACC_VOLATILE
JVM层面(规范)
为了实现 volatile 的内存语义,JMM会限制特定类型的编译器和处理器重排序,JMM会针对编译器制定 volatile 重排序规则表,“NO”表示禁止重排序。
编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
- 在每个 volatile 写操作的 前面插入
StoreStore
屏障,后面插入StoreLoad
屏障
- 在每个 volatile 读操作的 后面插入
LoadLoad
和LoadStore
屏障
Hotspot实现
int field_offset = cache->f2_as_index();
if (cache->is_volatile()) {
if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
OrderAccess::fence();
}
}
orderaccess_linux_x86.inline.hpp
inline void OrderAccess::fence() {
// 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
compiler_barrier();
}
如果硬件架构本身已经保证了内存可见性(单核处理器、一致性足够的内存模型),或者硬件架构本身不进行处理器重排序,或者有更强的重排序语义(能够分析多核间的数据依赖),那么volatile就是一个空标记,不会插入相关语义的内存屏障。
查看源代码,可以看到在汇编层面,带有 volatile 修饰的变量前面会有lock add1 $0x0, (%rsp)
这么一串指令,由此可见 volatile 是通过LOCK指令前缀来达到内存屏障的作用。
最后编辑于 : 2022.06.28 09:44:46 © 著作权归作者所有,转载或内容合作请联系作者
没有回复内容