volatile通过插入内存屏障禁止指令重排,确保new Singleton()的三步(分配内存、初始化、赋值引用)不被乱序执行,从而防止其他线程读到半初始化对象;它保证可见性与有序性,但不提供原子性。

volatile 怎么拦住指令重排
在双重检查锁定(DCL)单例中,instance = new Singleton() 看似一步,实则分三步:① 分配内存空间;② 调用构造方法初始化对象;③ 将引用指向该内存地址。JVM 或 CPU 可能将步骤③提前到②之前执行——即“分配内存 → 写引用 → 初始化”,导致其他线程看到一个已赋值但未初始化完成的对象(半初始化状态)。volatile 通过插入内存屏障阻止这种重排:写 volatile 变量前的所有操作(包括内存分配和构造调用)必须完成,才能执行写引用;读 volatile 变量后的操作也不能被提前到读之前。
为什么没 volatile 就可能返回 null 或异常对象
假设线程 T1 正在执行 new Singleton(),刚做完①③就切换走,此时 instance 不为 null;线程 T2 进入 getInstance(),判断 instance != null 直接返回——但该对象的字段(如 int i = 9)可能还是默认值 0,甚至构造函数里的逻辑根本没执行。这会造成:
- 字段值错误(例如 i 读出来是 0 而非 9)
- 调用方法时触发 NullPointerException(因依赖未初始化资源)
- 对象状态不一致,行为不可预测
volatile 的实际作用边界
它只解决两个问题:一是保证对 instance 引用的写操作对所有线程立即可见,二是确保 new 过程的三步不会被重排。但它不保证构造方法内部的线程安全,也不提供原子性——比如 instance++ 这类复合操作仍需 synchronized 或 AtomicInteger。所以 volatile 是 DCL 正确性的必要条件,不是万能锁。
正确写法与关键点
标准实现中必须同时满足三项:
立即学习“Java免费学习笔记(深入)”;
- instance 声明为 private static volatile Singleton instance
- 外层 null 检查(避免无谓加锁)
- 内层同步块中再次检查(防止重复初始化)
缺一不可。只要 volatile 缺失,高并发下就存在概率复现半初始化问题,尤其在服务启动、压测或 JIT 优化活跃的场景中更易暴露。


















