并发专题
什么是 volatile
能够保证线程可见性,当一个线程修改共享变量时,能够保证对另外一个线程可见性,不能保证原子性
volatile 的特性
- 保证可见性
- 禁止重排序
- 不能保证原子性
volatile实现
- 通过汇编lock前缀指令触发底层锁的机制
- 通过lock前缀指令,会锁定变量缓存行区域并写回主内存,这个操作称为“缓存锁定”
- 一处理器的缓存回写到内存会导致其他处理器的缓存无效
volatile hotspot实现
src/hotspot/share/interpreter/bytecodeinterpreter.cpp
判断是否为 volatile 的,且 CPU 平台支持多核核 atomic 操作则调用调用 OrderAccess::fence()
int field_offset = cache->f2_as_index();
if (cache->is_volatile()) {
if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
OrderAccess::fence();
}
src/hotspt/os_cpu/linux_x86/orderAccess_linux_x86.hpp
x86 架构实现OrderAccess::fence(),使用 lock addl 指令实现的,利用lock实现类似内存屏障的效果
inline void OrderAccess::storeload() { fence(); }
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();
}
Lock前缀指令作用
- LOCK前缀指令禁止读写指令重排序,具有类似于内存屏障的功能
- LOCK前缀指令会等待它之前所有的指令完成、并且所有缓冲的写操作写回内存
什么是重排序
- Java内存模型允许编译器和处理器对指令代码实现重排序提高运行的效率
- 重排序需要遵循as-ifserial语义
- as-ifserial:不管怎么重排序,单线程程序执行结果不会发生改变的
- 多核多线程的情况下重排序可能存在乱序问题
解决重排序问题
- volatile关键字解决了编译器层面的可见性与重排序问题
- 内存屏障解决了硬件层面的可见性与重排序问题
内存屏障解决重排序
- 写内存屏障:在指令后插入Store Barrier,能够让写入缓存中的最新数据更新写入主内存中,让其他线程可见。强制写入主内存CPU不会
- 读内存屏障:在指令前插入load Barrier ,可以让告诉缓存中的数据失效,强制从新主内存加载数据。强制读取主内存,让cpu缓存与主内存保持一致,避免缓存导致的一致性问题
- 内存屏障主要阻止屏障两边的指令重排序和刷新处理器缓存/冲刷处理器缓存
内存屏障分类
- lfence,是一种Load Barrier 读屏障
- sfence, 是一种Store Barrier 写屏障
- mfence, 是一种全能型的屏障,具备lfence和sfence的能力
- Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能
synchronized 与 volatile 区别
- Volatile保证线程可见性,当工作内存中副本数据无效之后,主动读取主内存中数据
- Volatile可以禁止重排序的问题,底层使用内存屏障
- Volatile不会导致线程阻塞,不能够保证线程安全问题(不能保证原子性),synchronized 会导致线程阻塞,能够保证线程安全问题