volatile在懒汉式单例中是必须项,用于禁止new Singleton()的指令重排序(如先赋引用后初始化),防止其他线程读到半初始化对象;它保障有序性与可见性,配合双重检查锁实现高效且线程安全的延迟初始化。

volatile 关键字在懒汉式单例中不是可选项,而是必须项——它解决的是对象构造完成前就被其他线程看到的问题,也就是指令重排序导致的“半初始化对象”风险。
为什么懒汉式必须加 volatile
普通懒汉式在多线程下可能创建多个实例;加 synchronized 能解决线程安全,但性能差;双重检查锁(DCL)兼顾效率与安全,但若不加 volatile,仍可能出错。
原因在于:new Singleton() 不是原子操作,它实际分三步:
- 在堆上分配内存空间
- 调用构造方法初始化对象
- 将引用赋值给静态变量 instance
JVM 和 CPU 可能对第2、第3步重排序——即先赋值引用(instance 指向一块未初始化完毕的内存),再执行构造。此时另一个线程进入 getInstance(),发现 instance != null,直接返回这个“半成品”,后续调用会触发 NullPointerException 或不可预期行为。
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
volatile 的作用就是禁止这种重排序,并保证该变量的写操作对所有线程立即可见。
正确写法:双重检查锁 + volatile
核心要点:静态变量声明必须用 volatile 修饰;getInstance() 中两次判空;同步块内再次判空;构造器私有。
示例代码:
public class LazySingleton {
private static volatile LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (LazySingleton.class) { // 加锁
if (instance == null) { // 第二次检查(有锁)
instance = new LazySingleton(); // volatile 保证此行不会被重排序
}
}
}
return instance;
}
}
常见错误写法对比
- 漏写 volatile:instance 可能被重排序,导致其他线程拿到未初始化完成的对象
- 只加 synchronized 到整个方法:每次调用都加锁,失去懒加载的性能优势
- 只做一次判空(无双重检查):加锁范围过大,或并发高时仍可能重复创建
- 用 static 代码块替代 DCL:变成饿汉式,失去延迟加载特性
替代方案简要说明
如果不想处理 volatile 和锁的细节,可考虑更简洁安全的方案:
- 静态内部类:利用类加载机制保证线程安全和延迟加载,无需 volatile 或 synchronized
- 枚举单例:天然线程安全、防反射、防序列化破坏,JVM 层保障唯一性
但 DCL + volatile 仍是面试高频点和中大型项目中常见实现,理解其原理和写法非常必要。

















