双重检查锁定需用volatile修饰实例变量,因其禁止jvm指令重排序(防止“分配内存→赋值引用→初始化对象”被错序),并保证可见性;否则线程可能获取到未初始化完成的半成品对象。

Java 中双重检查锁定(Double-Checked Locking)是实现线程安全单例的一种经典方式,核心在于减少同步开销,同时保证多线程下只创建一个实例。关键点是:必须用 volatile 修饰实例变量,否则可能因指令重排序导致其他线程看到未初始化完成的对象。
为什么需要 volatile?
在没有 volatile 的情况下,JVM 可能将以下三步重排序:
- 1. 分配内存空间
- 2. 初始化对象(调用构造方法)
- 3. 将引用赋值给静态变量 instance
若步骤 2 和 3 被重排,线程 A 执行到第 3 步时,instance 已非 null,但对象尚未初始化完成;此时线程 B 进入第一次 if 判断,直接返回这个“半初始化”的对象,引发异常或不可预期行为。volatile 禁止重排序,并保证可见性。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
标准实现写法
以下是推荐的双重检查锁定单例代码:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查(加锁后)
instance = new Singleton(); // volatile 保证原子性与可见性
}
}
}
return instance;
}
}
常见错误与注意事项
- 忘记加 volatile —— 这是最常见的致命错误,会导致不可靠的单例
- 把 synchronized 锁在 this 或局部对象上 —— 必须锁在类对象(
Singleton.class)上,确保全局唯一锁 - 构造方法未设为 private —— 外部仍可通过 new 创建实例,破坏单例语义
- 不适用于有参数的构造场景 —— 双重检查本身不支持传参初始化,需配合静态工厂或延迟加载策略
替代方案简要对比
虽然双重检查锁定有效,但更简洁安全的方式是使用静态内部类(利用类加载机制保证线程安全且无同步开销),或直接用枚举(天然防反射、反序列化攻击):
- 静态内部类:延迟加载 + 无同步 + JVM 保证线程安全
- 枚举单例:最简、最安全,但无法继承、不支持懒加载(类加载即初始化)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










