volatile是对共享变量的修饰词,无法修饰局部变量。它保证了被修饰变量的可见性和禁止指令重排。
一、JMM和volatile
JMM里volatile的规则:每个对变量 x 的读都能看到一个对 x 的写。所有对 volatile 变量的读写都是 volatile 动作。
该规则显示声明了volatile对可见性的保证,及暗含了禁止重排序的约定。
二、volatile在各层的实现机制
内存栅栏保证了可见性和禁止重排序
1. Java语言
1)实现方式:用volatile修饰共享变量
2)主要作用:禁止Java编译器重排序指令
2. Java编译器
对volatile修饰的变量在java字节码中增加ACC_VOLATILE的flag,且不会该变量代码进行重排序;
3. 字节码级别
1)实现方式:ACC_VOLATILE修饰变量
2)主要作用:提示JVM解释器和JIT增加内存栅栏,并禁止它们对volatile相关指令重排序;
4. JVM解释器和JIT
使用JVM的OrderAccess方法在volatile相关指令位置增加内存栅栏,且不会该变量代码进行重排序;
// linux_x86下fence方法
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
}
}
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级别有序;
三、CPU中LOCK指令保证可见性详解
1. 优先锁缓存行
1)CPU保证锁单个缓存行的原子性;
2)效率高,不会阻塞总线上的信号;
3)各CPU间,各内核间通过嗅探机制和MESI协议进行通讯,机制比较复杂;
2. 锁总线是最后的选择
1)如果变量跨缓存行,则需启用总线锁;
2)开销较大,在总线锁期间总线不会响应其他信号;
3)实现简单,是早期CPU的实现方式;
Note:
1)一个缓存行一般为64byte;
2)常用的缓存一致性协议为MESI,标识缓存行的四种状态Modify,Exclude,Shared,Invalid;
四、Unsafe实现volatile
Unsafe提供了内存栅栏的方法,如下:
public final class Unsafe {
/**
* Ensures lack of reordering of loads before the fence
* with loads or stores after the fence.
* @since 1.8
*/
public native void loadFence();
/**
* Ensures lack of reordering of stores before the fence
* with loads or stores after the fence.
* @since 1.8
*/
public native void storeFence();
/**
* Ensures lack of reordering of loads or stores before the fence
* with loads or stores after the fence.
* @since 1.8
*/
public native void fullFence();
...
}
和volatile的区别
1)Unsafe可自主决定栅栏的位置,较volatile灵活。例如,可以跨方法;有点类似synchronize和Lock的区别;
网友评论