23种设计模式——单例模式:独一无二的王者设计模式
·
👑 单例模式:独一无二的王者设计模式 👑
“在我的代码王国里,只能有一个国王!” —— 单例模式宣言 👑
🌟 单例模式是什么?
想象一下:
- 太阳系只能有一个太阳 ☀️
- 一个国家只能有一个国王 👑
- 一台电脑只能有一个任务管理器 💻
这就是单例模式!它确保一个类只有一个实例,并提供全局访问点。就像你永远不需要第二个任务管理器一样!
它是一种创建型的模式!
🧠 为什么要用单例模式?
| 场景 | 没有单例 😱 | 使用单例 😎 |
|---|---|---|
| 数据库连接 | 每次操作都新建连接,资源爆炸! | 全局共享一个连接池 |
| 配置管理器 | 配置被重复加载,内存泄漏 | 所有模块共享同一配置 |
| 日志系统 | 日志文件被多线程覆盖 | 统一日志入口 |
核心价值:
- 🚫 防止重复创建:节约系统资源
- 🌐 全局访问点:任何地方都能获取同一实例
- ⚡ 延迟初始化:需要时才创建(懒汉式)
💻 代码实战:五种单例实现
1. 饿汉式(饿肚子也要先做饭)
public class Sun {
// 类加载时就创建实例
private static final Sun instance = new Sun();
// 私有构造!禁止外部new
private Sun() {}
public static Sun getInstance() {
return instance;
}
}
// 使用:Sun.getInstance().shine();
✅ 优点:简单、线程安全
❌ 缺点:可能提前占用资源
2. 懒汉式(饿了才做饭)
public class King {
private static King instance;
private King() { /* 加冕仪式 */ }
public static synchronized King getInstance() {
if (instance == null) {
instance = new King();
}
return instance;
}
}
// 使用:King.getInstance().rule();
✅ 优点:延迟初始化
❌ 缺点:同步锁影响性能
3. 双重检查锁(智能管家)
public class TaskManager {
private static volatile TaskManager instance;
private TaskManager() {}
public static TaskManager getInstance() {
if (instance == null) {
synchronized (TaskManager.class) {
if (instance == null) {
instance = new TaskManager();
}
}
}
return instance;
}
}
✅ 优点:高性能+线程安全
⚠️ 注意:volatile关键字必不可少!
4. 静态内部类(优雅贵族)(最推荐使用的)
public class LogMaster {
private LogMaster() {}
private static class Holder {
static final LogMaster INSTANCE = new LogMaster();
}
public static LogMaster getInstance() {
return Holder.INSTANCE;
}
}
✅ 优点:线程安全+延迟加载+无锁
🎩 最优雅的实现方式!
5. 枚举式(终极王者)
public enum Universe {
INSTANCE;
public void bigBang() {
System.out.println("💥 宇宙大爆炸启动!");
}
}
// 使用:Universe.INSTANCE.bigBang();
✅ 优点:绝对防止反射攻击
👑 《Effective Java》作者Josh Bloch推荐
💼 面试黄金问答宝典
Q1:什么是单例模式?使用场景?
满分回答:
"单例模式确保一个类只有一个实例,并提供全局访问点。它就像公司的CEO,整个系统只需要一个。
使用场景:
- 需要共享资源的场景(数据库连接池、线程池)
- 需要控制资源的场景(配置管理器、日志系统)
- 需要唯一性的场景(任务管理器、序列号生成器)
Q2:如何实现线程安全的单例?
技术流回答:
"五种实现方式各有特点:
- 饿汉式:类加载时初始化,线程安全但可能浪费资源
- 同步懒汉式:简单但性能差
- 双重检查锁:高性能线程安全,注意volatile防重排序
- 静态内部类:利用类加载机制保证线程安全,推荐!
- 枚举单例:最安全,能防止反射攻击,Effective Java推荐"
Q3:单例模式有什么缺点?
辩证性回答:
"虽然单例很强大,但要注意:
- 测试困难:全局状态难以mock
- 违反单一职责:同时管理实例和业务逻辑
- 内存泄漏:长期持有可能不被GC回收
- 过度使用:不是所有’只需要一个’的场景都适用
🔍 设计挑战台
挑战1:单例 vs 静态工具类
// 静态工具类
class MathUtils {
public static int add(int a, int b) { return a + b; }
}
// 单例类
class ConfigManager {
private static ConfigManager instance;
private Map<String, String> configs;
private ConfigManager() { loadConfigs(); }
public static ConfigManager getInstance() { /*...*/ }
public String getConfig(String key) { /*...*/ }
}
思考:什么时候该用单例而不是静态方法?
💡 答案:当需要状态管理(如配置数据)时用单例,纯工具操作用静态方法。
挑战2:破坏单例的三种方式
-
反射攻击:通过反射调用私有构造器
Constructor<King> constructor = King.class.getDeclaredConstructor(); constructor.setAccessible(true); King fakeKing = constructor.newInstance(); -
序列化攻击:序列化后再反序列化
// 序列化 ObjectOutputStream oos = new ObjectOutputStream(...); oos.writeObject(King.getInstance()); // 反序列化 ObjectInputStream ois = new ObjectInputStream(...); King clone = (King) ois.readObject(); // 新实例! -
克隆攻击:实现Cloneable接口
@Override protected Object clone() throws CloneNotSupportedException { return super.clone(); // 返回新实例! }
防御策略:
- 使用枚举单例
- 重写
clone()方法抛出异常 - 实现
readResolve()方法返回已有实例
🌈 单例模式总结图
应用场景:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 配置管理器 │ │ 数据库连接池 │ │ 日志系统 │
└──────────────┘ └──────────────┘ └──────────────┘
▲ ▲ ▲
│ │ │
└─────────┬────────┴────────┬─────────┘
│ 单例模式核心思想 │
└─────────────────┘
▼
┌───────────────────────────────┐
│ 确保唯一实例 + 全局访问点 │
└───────────────────────────────┘
▼
┌───────┬───────┬───────┬───────┐
│ 饿汉式│ 懒汉式│双重检查│ 枚举式│
└───────┴───────┴───────┴───────┘
🎓 终极面试加分金句
“单例模式是控制欲最强的设计模式,但记住:权力越大,责任越大!
在Spring中,默认Bean作用域就是单例,但要注意线程安全问题。
在并发场景下,我首选静态内部类实现,平衡了线程安全和延迟加载的需求。”
更多推荐
所有评论(0)