连载中 6/21

volatile(下):指令重排与 happens-before 秩序公约

2026-10-25 · 68 阅读 · 0 评论 · 0 赞

你的代码不是按你写的顺序跑的

为了性能,编译器会重排指令,CPU 会乱序执行——前提只有一条:单线程内结果不能变(术语叫 as-if-serial,你写出来的逻辑等价于按顺序执行的效果)。麻烦在于:单线程的「结果不变」管不到别的线程的视角。重排在单线程里天衣无缝,落到另一个线程眼里,就是偶现的灵异事件。

灵异现场:没初始化完的对象

new 一个对象不是原子动作,它在 JVM 层面分三步:分配内存 → 初始化字段 → 把引用指过去。第 2、3 步之间允许重排——先把引用指过去,再慢慢初始化。单线程无感;但另一个线程此刻检查「引用非空」就直接用,拿到的是一个字段全为默认值的半成品对象。这不是玄学,是指令重排的教科书案例。

happens-before:JMM 的秩序公约

靠什么判断「A 线程的写,B 线程能不能看见」?JMM 给出 happens-before 公约——只要满足以下任意一条,前一个操作的结果对后一个操作可见且有序:程序顺序规则(单线程内,按代码顺序——重排不破坏结果即可)、锁规则(同一把锁,解锁先于后续加锁)、volatile 规则(写先于后续读)、线程规则(start 先于子线程任何动作、子线程动作先于 join 返回)、传递性(A 先于 B、B 先于 C,则 A 先于 C)。一条都不满足?JMM 不承诺任何事——可能是旧值,可能是重排后的鬼故事。所谓写并发代码,本质上就是给线程之间的读写凑出 happens-before 链条。

实战考场:DCL 单例为什么要 volatile

class Singleton {
    private static volatile Singleton instance;  // volatile 不能省

    static Singleton getInstance() {
        if (instance == null) {                  // 第一次检查:无锁
            synchronized (Singleton.class) {
                if (instance == null) {          // 第二次检查:持锁
                    instance = new Singleton();  // 三步,可被重排
                }
            }
        }
        return instance;
    }
}

第一次检查不加锁,是为了让 99% 的调用直接返回、不排队——但这恰恰给重排留了空子:线程 A 在 synchronized 里 new 到一半(引用已赋值、字段没初始化完),线程 B 在第一次检查看到 instance 非空,直接 return 一个半成品。volatile 禁止「初始化字段」和「引用赋值」的重排,堵死这条通道。DCL 的价值不只在单例本身——它是理解重排与 happens-before 最好的教具。顺带一个更省心的写法:静态内部类单例,靠 JVM 类初始化自带的锁语义天然线程安全,还自带懒加载,不写 volatile 也不出错。

volatile 的小抄

完整语义到齐:可见性(写立即可见)、有序性(禁止特定重排)、没有原子性。三宗罪里它治两宗,剩下一宗——原子性——交给下一篇的主角:CAS。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#Java#volatile#指令重排#happens-before#DCL#JUC

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10113 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9876 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9306 阅读 · 21 评论 · 287 赞