volatile 是 DCL 单例安全的必要条件,用于禁止指令重排和保证可见性;正确写法为 private static volatile Singleton instance;,两次判空分别用于性能优化与安全兜底。

volatile 在双重检查锁定(DCL)单例中不是锦上添花,而是安全底线。它专治 JVM 和 CPU 指令重排导致的“半初始化对象”问题——没有它,DCL 就不可靠。
volatile 必须修饰静态实例变量
声明只有一种正确形式:private static volatile Singleton instance;
- 缺 static:就不是类级单例,每个类加载器或线程可能产生多个实例
- 缺 volatile:new Singleton() 的三步(分配内存→调用构造器→赋值引用)可能被重排为「1→3→2」,另一个线程看到非 null 引用,却读到未初始化字段(如 int 字段为 0 而非预期值),甚至触发 NullPointerException
- 不能用 synchronized 替代:锁能保证临界区互斥,但管不了构造完成前的赋值对其他线程是否可见
为什么两次判空都必不可少
第一次判空在 synchronized 外,是性能优化:实例已存在时直接返回,不抢锁;第二次判空在 synchronized 内,是安全兜底。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 假设线程 A 和 B 同时看到 instance == null,都通过第一次检查
- 线程 A 抢到锁、创建实例、释放锁;此时 instance 已非 null
- 线程 B 等锁成功后若不二次检查,会再次 new,破坏单例
volatile 如何堵住重排漏洞
它靠两层机制协同生效:
立即学习“Java免费学习笔记(深入)”;
- 禁止重排序:在 instance = new Singleton() 的写操作前后插入内存屏障,强制构造方法执行完毕(步骤 2)再执行引用赋值(步骤 3)
- 保证可见性:每次读 instance 都从主内存加载,每次写都立即刷回主内存,避免线程读到本地缓存中的旧值或部分更新状态
- 建立 happens-before 关系:对 volatile 变量的写操作,happens-before 后续任意线程对该变量的读操作——这意味着读线程一定能看见构造函数内所有已完成的初始化动作
完整写法要点总结
以下结构缺一不可:
- 私有构造函数(防止外部 new)
- 静态 volatile 实例变量
- public static getInstance() 方法
- 外层 if (instance == null) 判空
- synchronized(Singleton.class) 块保护创建逻辑
- 块内再次 if (instance == null) 判空
- 块内执行 instance = new Singleton()

















