volatile在DCL中必须修饰instance,它通过内存屏障禁止“分配内存→赋值引用→初始化”的重排序(如1→3→2),确保线程读到的instance要么为null,要么是完全初始化的对象,从而防止半初始化漏洞。

volatile 在双重检查锁定(DCL)中防止对象半初始化,核心是堵住 JVM 和 CPU 指令重排序带来的“赋值早于初始化”漏洞——它不靠锁,而是用内存屏障强制执行顺序,并确保构造完成的状态对所有线程立即可见。
为什么 new Singleton() 实际会出问题
表面上 instance = new Singleton() 是一条语句,但 JVM 执行时拆成三步:
- 在堆中分配内存空间
- 调用构造方法初始化对象(字段赋值、资源加载等)
- 将引用赋值给
instance变量
若 instance 没有被 volatile 修饰,步骤 2 和 3 可能被重排为「1→3→2」。此时另一个线程执行外层 if (instance != null) 就会拿到一个指向已分配但未初始化内存的引用,后续调用方法大概率抛 NullPointerException,或读到字段默认值(如 int 字段为 0 而非预期值)。
volatile 怎么从根源上拦截半初始化
volatile 通过插入内存屏障实现两点关键约束:
立即学习“Java免费学习笔记(深入)”;
- 写屏障(Store Barrier):确保「构造方法执行完成」一定发生在「
instance赋值」之前,禁止 2 和 3 重排 - 读屏障(Load Barrier):其他线程读
instance时,必须看到其最新值,且能看到该值写入前所有已完成的内存操作(包括构造中的字段写入)
这就建立起跨线程的 happens-before 关系:线程 A 完成构造并写入 instance → 线程 B 读到非 null 的 instance → 线程 B 一定能安全使用该对象的所有字段和方法。
正确写法与常见错误
必须同时满足以下三点,缺一不可:
-
声明必须是
private static volatile Singleton instance;——static保证类级唯一,volatile保障有序与可见 - 不能用
synchronized替代volatile—— 锁只管临界区内的执行顺序,管不了锁外的赋值可见性 - 不能只加
volatile却漏掉双重检查 —— 单靠volatile无法防止多个线程同时进入构造,仍需锁+二次判空
它不解决什么
volatile 不提供原子性,也不替代锁的互斥作用:
-
instance = new Singleton()中的构造过程本身仍需同步保护(否则多个线程可能并发执行构造) -
volatile int count;的count++仍是非原子操作,不能靠它实现计数器
它的角色很明确:守住「引用赋值」这一关,让其他线程永远拿不到半成品。


















