核心概念
volatile 是Java提供的轻量级同步机制,主要解决两个问题:
- 可见性:一个线程修改了共享变量,其他线程能立即看到最新值
- 有序性:禁止指令重排序,保证代码执行顺序符合预期
底层实现原理
1. 可见性实现
volatile变量的读写会直接作用于主内存,而不是线程的工作内存:
// volatile变量的内存语义
public class VolatileExample {
private volatile boolean flag = false;
private int data = 0;
// 线程A:写操作
public void writer() {
data = 42; // 1. 普通写
flag = true; // 2. volatile写
}
// 线程B:读操作
public void reader() {
if (flag) { // 3. volatile读
int i = data; // 4. 普通读,此时一定能看到data=42
}
}
}
实现机制:
- 写操作:在volatile写之前,会将工作内存中的所有变量刷新到主内存
- 读操作:在volatile读之后,会使工作内存失效,强制从主内存重新加载
汇编层面:在X86架构下,volatile写会插入 lock 前缀指令,触发:
- 将当前处理器缓存行数据写回主内存
- 使其他处理器的缓存行失效(MESI缓存一致性协议)
2. 有序性实现
通过内存屏障(Memory Barrier)禁止特定类型的指令重排序:
| 屏障类型 | 说明 |
|---|---|
| LoadLoad | 禁止Load1与Load2重排序 |
| StoreStore | 禁止Store1与Store2重排序 |
| LoadStore | 禁止Load1与Store2重排序 |
| StoreLoad | 禁止Store1与Load2重排序(开销最大) |
volatile的内存屏障规则:
普通写 -> StoreStore屏障 -> volatile写 -> StoreLoad屏障
↓
LoadLoad屏障 ← volatile读
↓
LoadStore屏障
源码关键点
JVM规范中对volatile的happens-before规则:
// JMM的happens-before原则
// 规则1:对volatile变量的写操作 happens-before 后续对该变量的读操作
volatile int v = 0;
// 线程A
v = 1; // 写volatile
// 线程B
int temp = v; // 读volatile,一定能看到v=1
性能与线程安全考量
优势
- 性能优于synchronized:不会引起线程上下文切换和调度
- 细粒度控制:只保证单个变量的可见性和有序性
局限性
- 不保证原子性:
i++这类复合操作不适用 - 只能修饰变量:不能修饰方法或代码块
- 有序性有限:只能禁止volatile变量相关的重排序
适用场景
- 状态标志位(如开关控制)
- 单次安全发布对象(DCL双重检查锁)
- 读多写少的场景
实战示例
示例1:单例模式的DCL(双重检查锁)
public class Singleton {
// 必须使用volatile,防止指令重排序
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // 关键:这不是原子操作
}
}
}
return instance;
}
}
为何需要volatile? instance = new Singleton() 分为三步:
- 分配内存空间
- 初始化对象
- 将instance指向内存地址
指令重排序可能导致执行顺序变为1→3→2,其他线程可能拿到未初始化的对象。
示例2:状态标志位
public class StopThread {
private volatile boolean stop = false;
public void run() {
while (!stop) {
// 执行任务
}
}
public void shutdown() {
stop = true; // 立即对run()线程可见
}
}
答题总结
可见性保证:通过强制读写主内存 + 缓存一致性协议,确保变量修改对所有线程立即可见。
有序性保证:通过内存屏障禁止指令重排序,确保volatile变量前后的操作不会被乱序执行。
底层机制:汇编层面插入lock前缀指令,JMM层面遵循happens-before原则。
记忆口诀:”volatile保证可见有序,不保原子复合操作;内存屏障加锁指令,缓存一致协议保证。”