单例模式(Singleton Pattern)学习笔记
🌟 定义
单例模式属于创建型设计模式,确保一个类只有一个实例,并提供全局访问点。是Java中最简单但实现最复杂的设计模式。
🎯 适用场景
- 需要控制资源访问(如数据库连接池)
- 全局配置对象
- 日志记录器
- 设备管理器(如打印机服务)
- 缓存系统
- 线程池/连接池管理
🔧 模式结构
📐 类图
🛠️ 核心组成
- 私有静态实例:存储唯一实例
- 私有构造方法:防止外部实例化
- 静态获取方法:全局访问入口
- 线程安全措施:保证多线程环境正确性
📝 代码实现(6种经典写法)
1. 饿汉式(线程安全)
public class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {
// 防止反射攻击
if (INSTANCE != null) {
throw new IllegalStateException("Already initialized");
}
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
2. 懒汉式(非线程安全)
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
3. 同步方法懒汉式(线程安全)
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
4. 双重检查锁(DCL,线程安全)
public class DCLSingleton {
private volatile static DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if (instance == null) {
synchronized (DCLSingleton.class) {
if (instance == null) {
instance = new DCLSingleton();
}
}
}
return instance;
}
}
5. 静态内部类(线程安全)
public class InnerClassSingleton {
private InnerClassSingleton() {}
private static class SingletonHolder {
static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
6. 枚举式(最佳实践,线程安全)
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
// 业务方法
}
}
✅ 优点
- 严格控实例数量
- 全局唯一访问点
- 延迟初始化(部分实现)
- 减少内存开销(避免重复创建)
- 避免资源冲突
⚠️ 缺点
- 违反单一职责原则(同时控制创建和业务逻辑)
- 单元测试困难(全局状态难以隔离)
- 可能隐藏类间耦合关系
- 需要特殊处理序列化/反射攻击
- 长期持有可能造成内存泄漏
🔒 防御措施
1. 防止反射攻击
private Singleton() {
if (instance != null) {
throw new IllegalStateException("Already initialized");
}
}
2. 防止序列化破坏
// 添加readResolve方法
protected Object readResolve() {
return getInstance();
}
3. 防止克隆破坏
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
🔄 实现对比
实现方式 | 线程安全 | 懒加载 | 防止反射 | 防止序列化 | 性能 |
---|---|---|---|---|---|
饿汉式 | ✅ | ❌ | ❌ | ❌ | ⭐⭐⭐⭐ |
同步方法 | ✅ | ✅ | ❌ | ❌ | ⭐ |
双重检查锁 | ✅ | ✅ | ❌ | ❌ | ⭐⭐⭐ |
静态内部类 | ✅ | ✅ | ❌ | ❌ | ⭐⭐⭐⭐ |
枚举式 | ✅ | ❌ | ✅ | ✅ | ⭐⭐⭐⭐ |
💡 实践建议
- 优先选择枚举实现(Effective Java推荐)
- 需要懒加载时用静态内部类方式
- 避免使用synchronized方法(性能差)
- 谨慎使用单例模式(考虑依赖注入)
- 注意与Spring单例的区别:
- Spring单例是容器级的
- 设计模式单例是JVM级的
🚀 典型应用
-
Runtime类(JDK内置单例)
Runtime runtime = Runtime.getRuntime();
-
日志框架(LogManager)
Logger logger = LogManager.getLogManager().getLogger("");
-
Spring容器(ApplicationContext)
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
📌 扩展知识
1. 如何实现线程安全的延迟加载?
使用Initialization-on-demand holder
惯用法(静态内部类实现)
2. volatile关键字的作用?
- 保证可见性
- 禁止指令重排序
- 在DCL模式中防止返回未初始化完毕的对象
3. 为什么推荐枚举实现?
- 自动处理序列化
- 防止反射攻击
- 保证线程安全
- 代码简洁
掌握单例模式的关键在于理解实例控制和线程安全的平衡,根据具体场景选择合适的实现方式。在分布式系统、类加载器不同的环境等特殊场景下需要特别注意单例的有效范围。