Java 关键字 volatile

137 阅读2分钟

Java 关键字 volatile

volatile 是 Java 提供的最轻量级的线程同步关键字,它的核心作用只有两件事,却正好是解决多核 CPU 高速缓存 + 指令重排序的两大痛点:

  • 保证“可见性” 一个线程改了该变量,立刻对所有线程可见(跳过 CPU 缓存,直接刷回主内存)。

  • 禁止“指令重排序”(JDK 5 后新增) 变量前后的读写操作不会被编译器/CPU 打乱顺序,从而避免多线程下的“半初始化”问题。

  • 把变量声明为 volatile,读/写直接走主内存,立即可见。

  • 同时插入内存屏障,阻止编译器重排序。

  • 适用于一写多读或状态标志场景,复杂原子需求请用锁或原子类。

一句话记忆 “读直接从主内存拿,写完立即回主内存,并且不让编译器偷偷换顺序。”

典型使用场景

场景示例代码原因
状态开关volatile boolean running = true;一个线程改标志位,其他线程立刻看到停止指令。
单例双重检查private volatile static Singleton inst;防止 new 指令被重排序导致拿到“半初始化的对象”。
轻量计数器volatile long count;计数要求最终一致,而非严格原子递增(否则用 AtomicLong)。

代码演示:状态开关

class Task implements Runnable {
    private volatile boolean running = true;   // 1. 状态标志

    public void run() {
        while (running) {                      // 2. 工作循环
            // do something
        }
    }

    public void cancel() {
        running = false;                       // 3. 另一线程调用,立即可见
    }
}

不能做什么?

  • 不是原子操作:volatile i++ 仍非线程安全(需要 AtomicInteger 或加锁)。
  • 无法替代锁:不能保证多步操作的整体原子性。

在单线程程序中是否还有用?

单线程代码里写 volatile 是“无害但多余”的写法,既不提供正确性收益,也徒增轻微性能开销;只有在多线程共享变量时才需要使用它。

在纯粹的单线程程序里,volatile 关键字没有任何可见性或顺序性的实用价值,原因如下:

  1. 只有一条线程
    • 不存在第二个 CPU 核心、第二个线程去读或写该变量,因此:
    • 缓存一致性、可见性问题天然不存在;
    • 指令重排序即使发生,也不会被其他线程“看到”,不影响程序单线程语义。
  2. 编译器/JVM 的as-if-serial规则
    • 只要单线程执行结果不变,重排序就合法;volatile 插入的内存屏障在单线程里只是空耗性能。
  3. 仍然保留的语义
    • 禁止把 volatile 变量缓存在寄存器(每次读写必须走内存)。
    • 禁止与普通变量之间的重排序越过 volatile 访问。
    • 这两点在单线程下既无收益也无危害,只是平白增加一次内存访问。