核心概念

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

性能与线程安全考量

优势

  1. 性能优于synchronized:不会引起线程上下文切换和调度
  2. 细粒度控制:只保证单个变量的可见性和有序性

局限性

  1. 不保证原子性i++ 这类复合操作不适用
  2. 只能修饰变量:不能修饰方法或代码块
  3. 有序性有限:只能禁止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;
    }
}

为何需要volatileinstance = new Singleton() 分为三步:

  1. 分配内存空间
  2. 初始化对象
  3. 将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保证可见有序,不保原子复合操作;内存屏障加锁指令,缓存一致协议保证。”