双重校验锁单例需用volatile修饰实例变量,防止指令重排序导致其他线程看到未完全构造的对象;第一次判空无锁提升性能,第二次判空在同步块内确保仅初始化一次。

Java 中用 synchronized 实现双重校验锁(Double-Checked Locking)懒汉式单例,核心是**减少同步开销、保证线程安全、避免重复初始化**。关键在于:第一次判空不加锁,第二次判空在同步块内,且必须用 volatile 修饰实例变量。
为什么要用 volatile?
防止指令重排序导致其他线程看到未完全构造的对象。JVM 可能将对象创建拆为三步:① 分配内存;② 初始化对象;③ 将引用赋值给静态变量。若缺少 volatile,步骤②和③可能被重排,使其他线程拿到一个“半初始化”的实例,引发 NPE 或逻辑错误。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
标准写法(推荐)
以下是线程安全、高效、符合 JMM 的实现:
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(); // 非原子操作:分配 + 构造 + 赋值
}
}
}
return instance;
}
}
常见错误与避坑点
- 漏掉 volatile:会导致可见性问题,即使加锁也无法阻止重排序,是最大陷阱
-
把 synchronized 加在方法上:如
public static synchronized Singleton getInstance(),会锁整个方法,失去“双重校验”的性能优势 -
在构造器中暴露 this 引用:比如注册监听、启动线程时传入
this,可能让其他线程提前访问未构造完的对象(即使有 volatile 也救不了) - 使用非静态内部类或枚举更简单:若无需延迟加载到首次调用之外的复杂控制,推荐用静态内部类或枚举——它们天然线程安全且无须 volatile
为什么两次判空?
第一次判空避免绝大多数线程进入同步块,提升并发性能;第二次判空防止多个线程同时通过第一次检查后,在同步块里重复创建实例。只有第一个进入同步块且发现 instance == null 的线程才会真正初始化。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










