Java中用volatile配合双重检查锁实现懒加载单例,核心是首次创建时加锁、后续无锁访问,并通过volatile禁止指令重排序,确保线程安全获取完全初始化的实例;两次判空分别避免不必要的同步开销和重复初始化。

Java 并发编程中,用 volatile 配合双重检查(Double-Checked Locking)实现懒加载单例,核心在于:**只在首次创建时加锁,后续完全无锁访问,同时杜绝半初始化对象被其他线程看到的风险**。关键不在“写了没”,而在“为什么必须这么写”。
volatile 是防止重排序的生命线
对象构造不是原子操作,JVM 可能将其拆为三步:
- 分配内存空间
- 调用构造函数初始化字段
- 将
instance引用指向该内存地址
若没有 volatile,步骤②和③可能被重排序——即引用先被赋值,但字段还没初始化完。此时另一个线程执行第一次判空(instance != null),就会直接返回一个字段全为默认值(如 int=0、String=null)的对象,引发空指针或逻辑错误。volatile 强制禁止这种重排序,并保证写操作对所有线程立即可见,确保拿到的一定是完全构造好的实例。
两次判空各自承担不可替代的作用
第一次判空(同步块外):
避免已初始化后每次调用都抢锁,把高并发下的性能损耗降到最低。
立即学习“Java免费学习笔记(深入)”;
第二次判空(同步块内):
防止多个线程同时通过第一次检查后,争抢进入同步块,最终只允许一个线程真正执行构造;其余线程进入后发现 instance != null,直接返回,不重复创建。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
两者配合,达成“首次创建加锁,之后零开销”的目标。
标准写法与常见雷区
正确模板(JDK 1.5+):
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必须修饰静态字段instance,写在局部变量或参数上无效 - 不能用
synchronized(this)——此时this还不存在 - 构造方法里不能泄露
this(比如启动线程、注册回调),否则即使有volatile,其他线程也可能拿到未构造完的对象 - 不能用普通布尔标志位代替
volatile instance判空——无法保证可见性,等同于没锁
它适合什么场景,又有什么替代方案
适用场景:需要懒加载 + 高并发读多写少 + 对性能敏感(如配置中心、连接池工厂)。
更轻量推荐:静态内部类方式,利用 JVM 类加载机制天然线程安全且真正懒加载,代码更简洁,无须担心 volatile 写错;
更高安全性要求:枚举单例,天然防反射、防序列化攻击。


















