在单例设计模式中,懒汉式(Lazy Initialization) 通过延迟实例化来优化资源使用,但在多线程环境下存在线程安全问题。以下是其核心问题及解决方案的详细解析:
一、基础懒汉式代码(线程不安全)
public class Singleton { private static Singleton instance; private Singleton() {} // 私有构造器 public static Singleton getInstance() { if (instance == null) { // 步骤1:检查实例是否存在 instance = new Singleton(); // 步骤2:创建实例 } return instance; } }
问题分析
当多个线程同时调用 getInstance()
时:
-
线程A 进入步骤1,发现
instance
为null
。 -
线程B 同时进入步骤1,同样发现
instance
为null
。 -
两个线程都会执行步骤2,创建多个实例,违反单例原则。
二、解决方案
1. 同步方法(线程安全,效率低)
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }
-
优点:简单直接,确保线程安全。
-
缺点:每次调用
getInstance()
都需要同步,性能差(99% 情况下实例已存在,无需同步)。
2. 双重检查锁(Double-Check Locking,DCL)
public class Singleton { private static volatile Singleton instance; // volatile 禁止指令重排序 private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 第一次检查(无锁) synchronized (Singleton.class) { // 加锁 if (instance == null) { // 第二次检查(有锁) instance = new Singleton(); // 创建实例 } } } return instance; } }
关键点
-
双重检查:减少锁竞争,只有第一次创建实例时同步。
-
volatile
关键字:禁止 JVM 指令重排序,防止返回未初始化完成的实例。-
instance = new Singleton()
的代码实际分为三步:-
分配内存空间。
-
初始化对象。
-
将
instance
指向分配的内存。
-
-
若无
volatile
,可能发生指令重排(步骤3在步骤2之前执行),导致其他线程获取到未初始化的实例。
-
3. 静态内部类(推荐)
public class Singleton { private Singleton() {} private static class Holder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; // 类加载时初始化实例 } }
原理
-
JVM 在加载外部类时不会加载内部类,只有调用
getInstance()
时才会加载Holder
类。 -
类加载过程是线程安全的,由 JVM 保证,天然避免多线程问题。
4. 枚举实现(最佳实践)
public enum Singleton { INSTANCE; // 单例实例 public void doSomething() { // 业务方法 } }
优点
-
线程安全由 JVM 保证。
-
防止反射攻击(无法通过反射创建枚举实例)。
-
防止反序列化生成新对象。
三、方案对比
方案 | 线程安全 | 性能 | 实现复杂度 | 防反射/反序列化 |
---|---|---|---|---|
同步方法 | ✅ | ❌ | 低 | ❌ |
双重检查锁 | ✅ | ✅ | 中 | ❌ |
静态内部类 | ✅ | ✅ | 低 | ❌ |
枚举 | ✅ | ✅ | 低 | ✅ |
四、总结
-
基础懒汉式:多线程下不安全,需改进。
-
同步方法:简单但性能差,不推荐高并发场景。
-
双重检查锁:性能优,需配合
volatile
。 -
静态内部类:推荐方案,兼顾安全与性能。
-
枚举:最佳实践,支持防反射和反序列化。