跳转至

volatile 是如何保证可见性和有序性的?

一、可见性

volatile 修饰的变量,一个线程修改后,其他线程能立即看到新值。

原理

  • :改完立即刷新到主内存(或让其他核缓存行失效)。
  • :每次都从主内存(或最新缓存)读,不用本地缓存。

底层通过 MESI 协议:写 volatile 变量时,其他核的缓存行被标记为 I(Invalid),下次读必须重新从内存加载。

二、有序性

volatile 禁止指令重排:

  • 写 volatile 之前的操作不能排到 volatile 写之后。
  • 读 volatile 之后的操作不能排到 volatile 读之前。

典型应用:双重检查锁单例

private static volatile Singleton instance;

public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();   // volatile 防止重排
            }
        }
    }
    return instance;
}

new Singleton() 分三步:分配内存 → 初始化 → 引用赋值。没有 volatile 可能重排成 分配 → 引用赋值 → 初始化,其他线程拿到非 null 但未初始化的对象。

三、volatile 不保证原子性

volatile int i = 0;
i++;   // 不是原子操作!

i++ 是读-改-写三步,volatile 只保证每次读到最新值,不保证三步不被打断。要原子性用 AtomicIntegersynchronized

四、对比

volatile synchronized
可见性
有序性
原子性
性能 轻量(无锁) 重量(阻塞)

五、适用场景

  • 状态标志位:volatile boolean running = true;
  • 双重检查锁单例。
  • 一写多读。

常见误区

  • volatile 不能替代锁。
  • volatile 不能保证 i++ 原子性。