volatile 这题别只答「可见性」:面试官真正想听的是这条 happens-before

volatile 这题别只答「可见性」:面试官真正想听的是这条 happens-before

很多人答 volatile,开口就是「保证可见性,禁止指令重排」。不能说错,但这两句太像背书了,面试官往下问一句「那为什么 count++ 还是不行」,很容易就断在这里。

把它换成一条可推导的规则会稳很多:对同一个 volatile 变量,一次写入 happens-before 之后任意线程对它的读取。再加上线程内的代码顺序,前面的普通写就能跟着一起被发布出去。

看一个常见的状态发布场景:

java 复制代码
class TaskResult {
    private int value;
    private volatile boolean ready;

    void publish() {
        value = 42;
        ready = true;
    }

    int awaitResult() {
        while (!ready) {
            Thread.onSpinWait();
        }
        return value;
    }
}

当读线程看到 ready == true 时,它不该再看到旧的 value。理由不是「volatile 很神奇」,而是这条链:

text 复制代码
value = 42
  → ready = true(同一线程内的程序顺序)
  → 读取到 ready == true(volatile 写与读)
  → return value

Java 语言规范把这个关系叫 happens-before。前一个动作对后一个动作可见,并且在内存模型的语义上排在前面。

这里有个容易漏掉的前提:读线程得先读到那个 volatile 状态。要是代码先读 value,再看 ready,这条发布链就被你自己拆开了。

为什么 count++ 还是会丢

java 复制代码
class Metrics {
    private volatile int count;

    void record() {
        count++;
    }
}

count++ 不是一个不可分割的动作。它至少包含读旧值、加一、写回新值。

两个线程都读到 10,各自加一再写回 11,最后就少记了一次。volatile 让彼此更容易看到最新写入,但没有把这三个动作捆成一次。

这种场景该用 AtomicInteger.incrementAndGet(),或者用锁把「读、改、写」包起来。面试里可以直接按数据语义选:

  • 只是发布一个状态或引用:volatile 合适。
  • 需要一个数的原子增减:原子类合适。
  • 多个字段必须一起满足约束:锁更直白。

比如库存和已售数要同时更新,哪怕两者都加 volatile 也没有意义。问题不在单个字段看不见,而在这组更新不能被别的线程看见一半。

双重检查锁为什么一定要写 volatile

单例是这题最常见的追问:

java 复制代码
class Config {
    private static volatile Config instance;

    static Config getInstance() {
        if (instance == null) {
            synchronized (Config.class) {
                if (instance == null) {
                    instance = new Config();
                }
            }
        }
        return instance;
    }
}

没有 volatile 时,别只说「可能重排序」。把现象讲出来:另一个线程可能先看到一个非 null 的引用,却还没看到构造阶段写进去的完整状态。

instance 加 volatile 后,构造完成前的写入会随着这次 volatile 写发布;读到非 null 的线程再返回它,看到的是已经发布完成的对象状态。

这不表示 volatile 能替代 synchronized。这里的 synchronized 还在做「只能创建一次」的互斥,volatile 解决的是锁外那次读取能不能安全拿到对象。

面试时我会先问自己这三个问题

不用急着抛术语,先判断操作本身:

  1. 这是单次读写,还是读完还要根据旧值再写?
  2. 读线程有没有一个明确的状态变量,作为发布完成的信号?
  3. 这次修改会不会同时影响其他字段的不变量?

第一类才是 volatile 的主场。第二类决定状态发布能不能成立。第三类一旦回答「会」,基本就该往锁、原子类或并发容器上想了。

本文核对了 Java SE 21《Java Language Specification》第 17.4.4、17.4.5 节:volatile 写与后续读取的 synchronizes-with 关系,以及 happens-before 的传递规则。

相关推荐
王九思1 小时前
对称加密与非对称加密详解
java·网络·数据库
SimonKing1 小时前
Spring Boot 4.0 + gRPC 极简集成:4步搞定,比REST还丝滑
java·后端·程序员
C++、Java和Python的菜鸟1 小时前
第3章 后端Web基础 (Maven)
java
小小编程路1 小时前
Java super 关键字详解:用法、场景与常见误区
java·开发语言
我登哥MVP2 小时前
走进 Gang of Four 设计模式:外观模式
java·设计模式·外观模式
tiancaijiben2 小时前
阿里云SAE应用引擎弹性扩缩容完全指南:从手动调控到智能自动化
java·网络·数据库
增量星球2 小时前
《持续交付2.0系列一》从技术交付到业务引领
java·docker·容器·devops
ch.ju2 小时前
Java Programming Chapter 4——Anonymous inner class
java·开发语言
Mr__Miss2 小时前
Java 随机数生成器选型指南:Random、ThreadLocalRandom 与 SecureRandom
java·开发语言·python