精选·Java与并发

volatile 关键字与 Java 内存模型(JMM)

91学AI·2026/7/13·7 阅读

考察点

volatile 是并发面试的入门分水岭,答不出「不保证原子性」基本就出局了。面试官想确认你理解 JMM 为什么存在(CPU 缓存和指令重排带来的可见性、有序性问题),以及 volatile 和 synchronized 的能力边界。追问几乎必考双重检查锁,再往深会问 happens-before 和内存屏障。大数据场景里,flink 的 checkpoint 协调、各种开关标志位都能看到 volatile 的实际用法。

参考答案

JMM 要解决什么问题

Java 内存模型是个抽象规范,抹平不同硬件内存访问差异。核心结构:所有变量存在主内存,每个线程有自己的工作内存(类比 CPU 缓存),线程读写变量要先拷到工作内存,操作完再刷回主内存。由此引出并发三要素:

  • 原子性:操作不可中断。JMM 只保证基本类型读写的原子性,复合操作不保证。
  • 可见性:一个线程改了变量,其他线程能不能立刻看到。缓存不一致就是可见性问题。
  • 有序性:编译器和 CPU 为了性能会重排指令,单线程下保证语义不变(as-if-serial),多线程下重排可能让其他线程看到中间状态。

volatile 的两个保证

保证可见性。volatile 变量的写会立刻刷回主内存,读会绕过工作内存直接读主内存。底层是写操作后插入 StoreStore + StoreLoad 屏障,读操作前插入 LoadLoad + LoadStore 屏障。一个线程的修改对其他线程立即可见。

保证有序性(禁止指令重排)。内存屏障同时挡住了屏障两侧指令的重排序,写 volatile 之前的操作不会被排到写之后,读之后的操作不会被排到读之前。

不保证原子性,这是重点。经典反例:i++ 实际是「读 i、加 1、写回」三步,volatile 只保证每步读到的值是最新的,但两个线程可以同时读到 10、各自加 1、都写回 11,丢了一次更新。验证很简单:开 10 个线程每个对 volatile int 自增一万次,结果几乎必然小于十万。要原子性得用 synchronized 或 AtomicInteger(CAS)。

双重检查锁:volatile 的标准用例

public class Singleton {
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {                    // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {            // 第二次检查
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

这里的 volatile 不是为了可见性(synchronized 本身就能保证),而是为了禁止重排new Singleton() 看似一步,实际是三条指令:分配内存、初始化对象、把引用赋给 instance。如果不禁重排,JVM 可能先赋值引用再初始化,另一个线程在第一次检查时看到 instance 非 null,直接返回了一个还没初始化的对象,后续使用就 NPE。volatile 禁止了构造过程中的重排,保证对象完整初始化后引用才对其他线程可见。这是面试里最常现场写的代码,必须能默写并讲清每一步为什么。

什么时候用 volatile

适用场景很窄但明确:状态标志位(如 volatile boolean shutdown,一写多读,依赖值不依赖当前值)、双检锁单例、「读多写少且写入不依赖旧值」的场景。不适用:计数器、check-then-act 这类复合操作。和 synchronized 的取舍:volatile 轻量、不阻塞,但能力弱;synchronized 三项全保证但开销大。JDK 5 引入的 Atomic 系列(CAS 实现)填补了中间地带——比 synchronized 轻,又能保证复合操作原子性,统计类场景(比如大数据任务的计数指标)优先考虑。

可能的追问

  • happens-before 是什么? JMM 定义的偏序关系,A happens-before B 则 A 的结果对 B 可见。包括程序顺序规则、锁规则(解锁先于后续加锁)、volatile 规则(写先于后续读)、线程启动/中断/终止规则、传递性。它是判断并发正确性的理论工具。
  • volatile 和 synchronized 能互相替代吗? 不能。volatile 管可见性和有序性,不管原子性;synchronized 三项全管但会阻塞。标志位用 volatile 够,复合操作必须 synchronized 或 CAS。
  • i++ 不原子,那 volatile long/double 的读写原子吗? JLS 允许非 volatile 的 long/double 读写拆成两个 32 位操作(可能读到撕裂值),volatile 的 long/double 保证单次读写原子——但 volatile 的 long++ 依然不原子,别混淆。
  • 内存屏障有哪几种? LoadLoad、LoadStore、StoreStore、StoreLoad 四种。volatile 写前后加 StoreStore 和 StoreLoad,读后加 LoadLoad 和 LoadStore,其中 StoreLoad 开销最大(x86 上对应 lock 前缀指令)。

评论 (0)

暂无评论,快来抢沙发吧!

91学AI

© 2026 91学AI · 按岗位学 AI 与大数据. All rights reserved.