单例模式详解
单例模式(Singleton Pattern)是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。单例模式常用于需要全局唯一实例的场景,例如日志记录器、配置管理、线程池、数据库连接池等。
1. 单例模式的核心要点
- 唯一性:保证一个类在系统中只有一个实例。
- 控制访问:提供一个全局访问点来获取该实例。
- 延迟初始化(可选):实例化延迟到第一次使用时。
2. 单例模式的实现方式
2.1 饿汉式单例
- 特点:
- 实例在类加载时就创建。
- 简单但可能造成资源浪费(即使没有用到单例实例,仍会初始化)。
实现代码
public class Singleton {
// 在类加载时创建实例
private static final Singleton INSTANCE = new Singleton();
// 私有化构造方法,防止外部实例化
private Singleton() {}
// 提供全局访问点
public static Singleton getInstance() {
return INSTANCE;
}
}
- 优点:
- 简单实现,线程安全。
- 缺点:
- 提前加载实例,可能浪费资源。
2.2 懒汉式单例
- 特点:
- 实例在第一次调用时创建。
- 延迟初始化,但非线程安全(需要额外处理并发)。
实现代码
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 优点:
- 延迟加载,节省资源。
- 缺点:
- 非线程安全,可能在多线程环境下创建多个实例。
2.3 线程安全的懒汉式单例
(1) 同步方法
通过对 getInstance
方法加 synchronized
实现线程安全。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 优点:
- 线程安全。
- 缺点:
- 性能较低,每次访问都需要同步。
(2) 双重检查锁
双重检查锁在多线程环境下高效且线程安全。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 优点:
- 线程安全,性能较高。
- 使用
volatile
确保多线程环境中正确处理。
- 缺点:
- 实现较复杂。
2.4 静态内部类实现
- 特点:
- 利用 Java 的类加载机制实现延迟加载。
- 线程安全且高效。
实现代码
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
-
原理:
SingletonHolder
类在getInstance
方法第一次调用时才会加载。- Java 类加载机制确保
INSTANCE
的线程安全性。
-
优点:
- 延迟加载。
- 性能高,线程安全。
-
缺点:
- 无法支持非静态单例需求。
2.5 枚举实现
- 特点:
- 枚举类型的线程安全性由 Java 语言本身保证。
- 最简洁的实现方式,防止反射和序列化破坏单例。
实现代码
public enum Singleton {
INSTANCE;
public void someMethod() {
// 单例方法逻辑
}
}
- 优点:
- 简单、安全。
- 防止反射和序列化破坏。
- 缺点:
- 不支持延迟加载。
3. 单例模式的破坏与防御
3.1 破坏方式
(1) 反射破坏
通过反射调用私有构造方法,可以创建多个实例。
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance1 = constructor.newInstance();
Singleton instance2 = constructor.newInstance();
(2) 序列化破坏
通过序列化和反序列化,可以创建新的实例:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
oos.writeObject(Singleton.getInstance());
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton newInstance = (Singleton) ois.readObject();
3.2 防御措施
(1) 防止反射破坏
在构造方法中添加检查:
private Singleton() {
if (instance != null) {
throw new IllegalStateException("Instance already created");
}
}
(2) 防止序列化破坏
实现 readResolve
方法:
private Object readResolve() throws ObjectStreamException {
return instance;
}
4. 单例模式的优缺点
优点
- 控制实例数量,节省资源。
- 提供全局访问点,便于管理。
- 延迟初始化(可选),提升性能。
缺点
- 隐藏类依赖关系,可能增加代码耦合。
- 在多线程环境下实现较复杂。
- 无法灵活扩展多个实例。
5. 单例模式的使用场景
- 日志管理:全局统一日志记录器。
- 配置管理:全局唯一的配置文件管理器。
- 线程池:系统级别的线程池。
- 数据库连接池:全局统一管理数据库连接。
- 缓存管理:全局唯一的缓存实例。
6. 总结
实现方式 | 延迟加载 | 线程安全 | 实现难度 | 推荐场景 |
---|---|---|---|---|
饿汉式单例 | 否 | 是 | 简单 | 资源充足、实例创建成本低 |
懒汉式单例 | 是 | 否 | 简单 | 单线程场景 |
线程安全懒汉式 | 是 | 是 | 中等 | 多线程场景 |
双重检查锁 | 是 | 是 | 较复杂 | 多线程且需高性能的场景 |
静态内部类 | 是 | 是 | 简单 | 推荐,延迟加载且性能高 |
枚举单例 | 否 | 是 | 最简单 | 推荐,需防止反射和序列化破坏的场景 |
静态内部类和枚举实现是最推荐的单例实现方式,根据具体需求选择合适的实现即可。