在 Java 单例模式中,懒汉式 是一种延迟初始化 的实现方式,核心特点是实例在首次被使用时才创建(而非类加载时),避免了饿汉式可能造成的资源浪费,适用于实例占用资源大或使用频率低的场景。以下是懒汉式的常见实现方式、特点及注意事项:
一、懒汉式的核心思想
- 延迟实例化 :不随类加载创建实例,而是在第一次调用
getInstance()方法时初始化。 - 按需创建:仅当实例被实际使用时才分配资源,提高资源利用率。
二、懒汉式的实现方式
1. 基础实现(非线程安全)
最简单的懒汉式,但多线程环境下会创建多个实例,仅适用于单线程场景:
public class LazySingleton {
// 静态实例初始化为null,延迟初始化
private static LazySingleton instance;
// 私有构造器:禁止外部通过new创建实例
private LazySingleton() {}
// 公共方法:首次调用时创建实例
public static LazySingleton getInstance() {
if (instance == null) { // 未初始化时创建
instance = new LazySingleton();
}
return instance;
}
}
问题 :多线程同时进入if (instance == null)时,会创建多个实例,违背单例原则。
2. 同步方法(线程安全,但性能差)
通过synchronized修饰getInstance()方法,强制同一时间只有一个线程执行实例创建逻辑:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
// 同步方法保证线程安全
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
优缺点:
- ✅ 线程安全,实现简单;
- ❌ 每次调用
getInstance()都需加锁,高并发场景下锁竞争导致性能损耗。
3. 双重检查锁(DCL,Double-Checked Locking)
结合 "两次判空 + volatile" 优化,既保证线程安全,又减少锁开销,是懒汉式的经典优化方案:
public class LazySingleton {
// 必须用volatile修饰:防止指令重排序导致的"半初始化实例"问题
private static volatile LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
// 第一次判空:避免不必要的锁(大部分情况实例已存在)
if (instance == null) {
synchronized (LazySingleton.class) { // 类对象作为锁
// 第二次判空:防止多个线程等待锁后重复创建
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
关键细节:
volatile的作用:instance = new LazySingleton()会被拆分为 "分配内存→初始化实例→赋值引用" 三步,volatile禁止指令重排序,避免其他线程获取到 "未完全初始化的实例"(导致空指针)。- 双重判空:外层判空减少锁竞争,内层判空保证唯一实例。优缺点:
- ✅ 线程安全,高并发下性能优异;
- ❌ 实现稍复杂,需注意
volatile的使用(Java 5 + 才支持volatile禁止重排序)。
4. 静态内部类(最优实现)
利用 JVM 类加载机制实现懒加载 + 线程安全,是懒汉式的最优写法:
public class LazySingleton {
// 私有静态内部类:外部类加载时不会初始化,实现懒加载
private static class SingletonHolder {
// 静态常量:JVM保证类加载时的线程安全,仅初始化一次
private static final LazySingleton INSTANCE = new LazySingleton();
}
private LazySingleton() {}
public static LazySingleton getInstance() {
// 首次调用时,加载SingletonHolder并初始化INSTANCE
return SingletonHolder.INSTANCE;
}
}
原理:
- 外部类
LazySingleton加载时,内部类SingletonHolder不会被加载; - 首次调用
getInstance()时,SingletonHolder才会被加载,JVM 保证类加载的线程安全性,因此INSTANCE仅创建一次。优缺点: - ✅ 线程安全、懒加载、实现简洁、无性能损耗;
- ❌ 无法通过反射以外的方式传递初始化参数(如动态配置)。
三、懒汉式的应用场景
- 实例占用资源大且使用频率低:如数据库连接池、Redis 缓存客户端(避免程序启动时就创建重量级资源);
- 初始化依赖动态参数:如需要根据运行时配置初始化实例(饿汉式类加载时参数可能未就绪);
- 高并发但实例创建后访问频繁:双重检查锁或静态内部类实现可平衡线程安全与性能。
四、懒汉式 vs 饿汉式
| 特性 | 懒汉式 | 饿汉式 |
|---|---|---|
| 实例化时机 | 首次调用getInstance()时 |
类加载时 |
| 线程安全 | 需手动保证(如 DCL、静态内部类) | 天然线程安全(JVM 类加载机制) |
| 资源利用率 | 高(按需初始化) | 低(可能提前初始化闲置资源) |
| 实现复杂度 | 稍复杂(需处理线程安全) | 简单 |
总结
懒汉式单例模式通过延迟初始化 优化资源利用,其中静态内部类 实现是最优选择(线程安全、简洁高效),而双重检查锁适合需要动态参数初始化的场景。实际开发中需根据资源占用、并发需求选择合适的实现方式,确保单例的唯一性和性能。