1. 单例模式的双生子:懒汉与饿汉的本质差异
在面向对象编程中,单例模式就像公司里唯一的总裁办公室——无论多少人来访问,返回的都是同一个实例。而实现这个模式时,开发者常面临两个经典选择:懒汉模式(Lazy Initialization)和饿汉模式(Eager Initialization)。这两种方式在Android系统源码中随处可见,比如LayoutInflater采用的就是饿汉式单例。
关键区别在于实例创建的时机:饿汉模式像提前备好所有食材的厨师,类加载时就创建实例;懒汉模式则像现点现做的私房菜,等到第一次调用getInstance()时才初始化。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 饿汉模式:简单粗暴的提前加载
2.1 基础实现模板
java复制public class EagerSingleton {
// 静态常量在类加载阶段初始化
private static final EagerSingleton instance = new EagerSingleton();
// 私有化构造器
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
2.2 设计原理剖析
- 类加载机制:利用JVM类加载的特性(线程安全)保证实例唯一性
- 内存占用:从程序启动就常驻内存,适合初始化耗时短的轻量级对象
- 典型应用:Android中的系统服务(如WindowManager)
实际开发中遇到过这样的坑:如果单例依赖运行时参数,饿汉模式就无法满足需求。比如需要根据配置文件决定实例属性的场景。
3. 懒汉模式:按需加载的艺术
3.1 线程安全演进史
第一代(线程不安全版):
java复制public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton(); // 多线程会创建多个实例
}
return instance;
}
}
第二代(方法同步锁版):
java复制public synchronized static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
第三代(双重检查锁DCL版):
java复制public class LazySingleton {
private volatile static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (LazySingleton.class) {
if (instance == null) { // 第二次检查
instance = new LazySingleton();
}
}
}
return instance;
}
}
3.2 关键知识点
- volatile关键字:防止指令重排序导致的空指针异常
- 锁粒度优化:相比方法级同步,DCL将性能影响降低80%以上
- 初始化陷阱:构造方法中不要调用getInstance(),会导致死锁
4. 性能对比与选型指南
4.1 基准测试数据(JMH测试结果)
| 模式类型 | 首次加载耗时 | 并发访问QPS | 内存占用 |
|---|---|---|---|
| 饿汉模式 | 0.3ms | 15,000 | 较高 |
| 懒汉DCL | 2.1ms | 12,800 | 按需 |
4.2 选型决策树
- 是否需要延迟加载?
- 是 → 懒汉模式
- 否 → 进入问题2
- 实例初始化是否耗时?
- 是 → 懒汉模式
- 否 → 进入问题3
- 是否在意启动性能?
- 是 → 懒汉模式
- 否 → 饿汉模式
5. 特殊场景下的变体实现
5.1 静态内部类实现(Holder模式)
java复制public class HolderSingleton {
private HolderSingleton() {}
private static class Holder {
static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() {
return Holder.INSTANCE; // 首次调用时加载Holder类
}
}
这种实现兼具懒加载和线程安全的优点,是Android开发中最推荐的写法。
5.2 枚举单例(Effective Java推荐)
java复制public enum EnumSingleton {
INSTANCE;
public void doSomething() {
// 业务方法
}
}
绝对防止反射攻击的实现方式,适合安全敏感场景。
6. 实际开发中的血泪教训
- 序列化问题:即使单例也要实现Serializable接口,并重写readResolve()方法
java复制protected Object readResolve() {
return getInstance();
}
-
单元测试困境:单例会导致测试用例相互污染,推荐引入依赖注入框架
-
内存泄漏预警:Context相关的单例(如Android中)必须使用Application Context
-
多进程失效:Android跨进程时单例会失效,需要改用AIDL等IPC机制
在最近开发的智能家居控制系统中,就因为错误使用饿汉模式导致App启动时间增加了400ms。后来改用Holder模式后,不仅解决了延迟加载问题,还意外发现内存占用降低了18%。这提醒我们:没有最好的模式,只有最适合场景的实现。
