
volatile
定义
- Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排它锁单独获取这个变量。
线程的可见性
1 | public class VolatileApplication { |
原理
java源码:
1 | instance = new Singleton(); //instance是volatile变量 |
汇编:
1 | 0x01a3deld:movb $0✖️0,0✖️1104800(%esi);oxo1a3de24:lock addl $0✖️0,(%esp); |
lock指令干了两件事情:
- 将当前处理器缓存行的数据写会到系统内存
- 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效
缓存一致性
- 由于CPU的运行速度远远大于向内存读取的速度,所以为了提高效率,加入了高速缓存机制,即处理器不会直接和内存进行通信,而是先将物理内存的数据读到内部缓存(L1,L2,L3或其他),再进行操作 。保存的一致性就是保证多个缓存中共享数据的一致性,目前主要是通过MESI协议来保证的。
MESI 协议
- Modified、Exclusive、 Share or Invalid状态而命名的,用来标识每个缓存行的状态(由两个状态位表示),每个缓存行(缓存的数据基本单位)都处于M、E、S和I这四种状态之一。
伪共享
- 当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。
1 | public class T02_CacheLinePadding { |
避免伪共享
缓存行隔离
- 利用缓存行在现代计算机中占8字64字节的特性,为避免多线程缓存一致性带来的性能影响,引入了缓存行对齐,在定义对象时,如果对象在内存中不占64字节,就补齐64字节,保证对象中的数据缓存在不同的缓存行中。
缓存行64个字节是CPU同步的基本单位,缓存行隔离会比伪共享效率要高
Concurrency with LMAX Disruptor – An Introduction
指令重排序
定义
- CPU内部的一个优化,当CPU同时先后执行两条指令,一条是从内存中读指令,一条是执行操作数据指令,CPU由于读指令更耗时,会对指令进行重新排序,先执行操作数据的指令,再执行读指令,当然是这两条指令的变量必须是没有关联,不会互相影响的。
1 | public class T04_Disorder { |
问题
- 对象的半初始化
1 | public class T { |
- DCL单例到底需不需要加volatile修饰符

这里的 volatile
关键字主要是为了防止指令重排。原理如下:
singleton = new Singleton();
这段代码其实是分为三步:
- 分配内存空间,t.m=0
- 初始化对象,执行构造函数,t.m=8
- 将
singleton
对象指向分配的内存地址
加上
volatile
是为了让以上的三步操作顺序执行,反之有可能第二步在第三步之前被执行就有可能某个线程拿到的单例对象是还没有初始化的,以致于报错。
JSR内存屏障
- 两条指令间如果加了内存屏障,CPU运行的时候就不能对指令进行重排序。
volitile如何解决指令重排序
- volatile i
- ACC_VOLATILE
- JVM的内存屏障
- hotspot实现
查看虚拟机中的C++源码文件如下:
bytecodeinterpreter.cpp
1 | int field_offset = cache->f2_as_index(); |
orderaccess_linux_x86.inline.hpp
1 | inline void OrderAccess::fence() { |