什么是单例模式?
单例模式(Singleton Pattern)是设计模式中最简单但最常用的一种。它的核心思想是:确保一个类只有一个实例,并提供一个全局访问点。
生活比喻
一个国家只能有一个总统(否则就乱套了)。这个总统的职位就是"单例"——全局唯一,只能有一个人担任。任何人想成为总统,都必须去找"总统"这个职位本身,而不是自己创建一个新的总统职位。
单例模式的核心特征:
1. 私有构造方法 - 外部不能 new 这个类
private Singleton() {}
2. 私有静态实例 - 类内部自己创建唯一实例
private static final Singleton INSTANCE = ...;
3. 公开静态方法 - 外部获取唯一实例的入口
public static Singleton getInstance() { return INSTANCE; }
为什么需要单例模式?
共享资源
全局共享的数据,如数据库连接池、配置管理器
减少开销
避免重复创建和销毁对象,提升性能
全局协调
统一管理共享资源,如日志记录器
四种实现方式详解
1. 饿汉式(推荐指数:★★★★☆)
类加载时就创建实例,线程安全,但可能造成资源浪费(如果从未使用)
public class HungrySingleton {
// 类加载时就创建实例
private static final HungrySingleton INSTANCE = new HungrySingleton();
// 私有构造方法
private HungrySingleton() {
System.out.println("创建 HungrySingleton 实例");
}
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
// 特点:
// ✓ 线程安全(类加载时就由JVM保证)
// ✓ 调用效率高(直接返回实例)
// ✗ 延迟加载?不支持(类加载时就创建)
2. 懒汉式(推荐指数:★★☆☆☆)
首次调用时才创建实例,支持延迟加载,但线程不安全(需要加synchronized)
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
System.out.println("创建 LazySingleton 实例");
}
// synchronized 保证线程安全,但性能开销大
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
// 特点:
// ✓ 延迟加载(需要时才创建)
// ✓ 线程安全(synchronized)
// ✗ 性能问题:每次调用都要获取锁
3. 双重检查锁(推荐指数:★★★★★)
既支持延迟加载,又是线程安全的,是最常用的单例实现方式
public class DoubleCheckSingleton {
// volatile 防止指令重排序
private static volatile DoubleCheckSingleton instance;
private DoubleCheckSingleton() {
System.out.println("创建 DoubleCheckSingleton 实例");
}
public static DoubleCheckSingleton getInstance() {
// 第一次检查:避免不必要的同步
if (instance == null) {
synchronized (DoubleCheckSingleton.class) {
// 第二次检查:防止多线程重复创建
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
// 特点:
// ✓ 延迟加载
// ✓ 线程安全
// ✓ 高性能(避免不必要的同步)
// 注意:volatile 关键字防止指令重排序
4. 静态内部类(推荐指数:★★★★★)
利用Java类加载机制实现延迟加载和线程安全,是最简洁的实现方式
public class StaticInnerSingleton {
private StaticInnerSingleton() {
System.out.println("创建 StaticInnerSingleton 实例");
}
// 静态内部类,JVM保证线程安全
private static class Holder {
private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
}
public static StaticInnerSingleton getInstance() {
return Holder.INSTANCE;
}
}
// 特点:
// ✓ 延迟加载(Holder类不会在Main类加载时加载)
// ✓ 线程安全(JVM类加载机制保证)
// ✓ 高性能(无需同步)
// ✓ 简洁:这是作者最推荐的方式!
四种实现方式对比
| 方式 | 延迟加载 | 线程安全 | 性能 | 推荐程度 |
|---|---|---|---|---|
| 饿汉式 | ✗ | ✓ | 高 | ★★★★☆ |
| 懒汉式 | ✓ | ✓ | 低 | ★★☆☆☆ |
| 双重检查锁 | ✓ | ✓ | 高 | ★★★★★ |
| 静态内部类 | ✓ | ✓ | 高 | ★★★★★ |
单例模式的破坏和防护
警告:单例模式可能被破坏!
// 1. 反射攻击
// 通过反射可以调用私有构造方法创建新实例
Constructor constructor = DoubleCheckSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
DoubleCheckSingleton s1 = constructor.newInstance();
// 防护:在构造方法中检查实例是否已存在
private DoubleCheckSingleton() {
if (instance != null) {
throw new RuntimeException("单例模式被破坏!");
}
}
// 2. 序列化攻击
// 通过反序列化可以创建新实例
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.db"));
oos.writeObject(DoubleCheckSingleton.getInstance());
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.db"));
DoubleCheckSingleton s2 = (DoubleCheckSingleton) ois.readObject();
// 防护:添加 readResolve 方法返回唯一实例
private Object readResolve() {
return getInstance();
}
单例模式的应用场景
1
数据库连接池
整个应用共享一个连接池,避免频繁创建销毁连接
2
配置管理器
全局配置信息,如数据库配置、服务器配置
3
日志记录器
如Log4j、SLF4J的Logger通常也是单例
4
任务调度器
系统中只能有一个任务调度中心
小结
- 单例模式确保一个类只有一个实例,并提供全局访问点
- 饿汉式:类加载时创建,线程安全,但不支持延迟加载
- 懒汉式:延迟加载,但synchronized导致性能低
- 双重检查锁:既延迟加载又线程安全,推荐使用
- 静态内部类:利用JVM类加载机制,推荐使用
- 注意防护反射和序列化对单例的破坏