双重检查加锁(dcl)是java中实现线程安全懒加载单例的经典方案,需配合volatile关键字防止指令重排序导致的半初始化问题。

在 Java 多线程环境中,双重检查加锁(Double-Checked Locking, DCL)是解决懒加载单例或高开销资源初始化时线程安全问题的经典方案。它不是“加一次锁就完事”,而是通过两次空值判断 + 一次同步块,把锁的粒度降到最低——只在真正需要创建实例时才加锁,且仅第一次调用才走锁路径。
为什么需要双重检查?
单次检查(比如只判 instance == null 再同步)不行:多个线程可能同时通过第一次判断,然后排队进同步块,但只有一个能创建实例,其余线程仍会执行 new Singleton() —— 这就破坏了单例。而单纯给整个方法加 synchronized 又太重,后续所有读操作都得排队。
双重检查的核心逻辑是:
- 先快速判断:实例已存在?→ 是,直接返回,不加锁
- 否,再进同步块;进块后再次判断:别人是否已经创建好了?→ 是,跳过创建;否,才真正初始化
必须加 volatile 关键字
即使写了双重检查,漏掉 volatile 依然可能出错。因为 new Singleton() 实际分三步:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 分配内存空间
- 初始化对象(调用构造函数)
- 将引用赋值给静态变量
instance
JVM 可能对后两步重排序(即先赋值引用,再完成初始化)。这时另一个线程看到 instance != null,直接返回,却拿到一个“半初始化”的对象——引发 NullPointerException 或状态异常。加 volatile 能禁止这种重排序,并保证写操作对其他线程立即可见。
标准写法与关键细节
以下是安全、可直接复用的模板:
public class ResourceManager {
private static volatile ResourceManager instance;
private ResourceManager() {
// 防反射攻击(可选增强)
if (instance != null) {
throw new RuntimeException("Use getInstance() to get the instance.");
}
// 初始化耗时资源,如连接池、配置加载等
}
public static ResourceManager getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (ResourceManager.class) { // 加锁
if (instance == null) { // 第二次检查(有锁)
instance = new ResourceManager(); // 安全创建
}
}
}
return instance;
}
}
注意点:
-
synchronized锁的是类对象(ResourceManager.class),确保全局唯一锁 - 构造函数私有,且内部可加防反射逻辑(非必须,但推荐)
- 不要在
getInstance()中做复杂计算或 I/O,否则会拖慢同步块执行
它适合什么场景?
DCL 主要用于:资源创建成本高(如数据库连接池、大缓存加载)、启动时不确定是否用到、且必须保证全局唯一实例的场景。如果资源初始化很快,或确定程序启动就一定会用,用饿汉式更简单安全;如果追求极致健壮(防序列化/反射破坏),可考虑枚举单例。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










