双重检查锁单例必须加volatile,以防止指令重排序和保证可见性;否则可能返回未初始化的“半成品”对象,导致NullPointerException等错误。

双重检查锁单例必须加 volatile,核心就两点:防止指令重排序、保证变量修改对其他线程立即可见。
避免对象未初始化就被读取
看似简单的一行 instance = new Singleton(),JVM 实际分三步执行:
- 为对象分配内存空间
- 调用构造方法初始化对象(设置字段、执行逻辑)
- 将引用赋值给
instance变量
没有 volatile 时,JVM 和 CPU 可能将第2步和第3步重排序——先让 instance 指向一块已分配但尚未初始化的内存。此时另一个线程恰好执行外层判空 if (instance != null),会直接返回这个“半成品”对象,后续调用其方法可能触发 NullPointerException 或读到默认值(如 0、null),造成不可预知错误。
确保多线程间的状态可见性
synchronized 块只保证块内操作的原子性和块边界处的可见性,但外层的判空检查(if (instance == null))不在同步范围内。这意味着:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 线程A在同步块内完成
instance = new Singleton()并退出后,其对instance的写入可能还停留在本地缓存中 - 线程B随后读取
instance,可能看到旧值(null)或过期值(未初始化的引用)
volatile 强制每次读都从主内存加载最新值,每次写都立即刷回主内存,让所有线程看到一致的状态。
volatile 提供的内存屏障保障
volatile 不是魔法,它通过插入内存屏障(Memory Barrier)来实现语义:
- 写操作后插入 StoreLoad 屏障:阻止后续普通读写与该 volatile 写重排,也确保前面的初始化已完成
- 读操作前插入 LoadLoad + LoadStore 屏障:保证读到的是最新写入,且后续读写不会被提前到该读之前
这些屏障共同封锁了重排序漏洞,使“先初始化、再赋值、再被读到”这一逻辑顺序在多线程下真正成立。
不加 volatile 的双重检查锁,在高并发场景下极难复现问题,但一旦发生,往往定位困难、后果严重。加上它,成本极低,却能一劳永逸堵住这个经典并发陷阱。


















