单例设计模式之懒汉式以及线程安全问题
在单例设计模式中,懒汉式(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
。 -
静态内部类:推荐方案,兼顾安全与性能。
-
枚举:最佳实践,支持防反射和反序列化。