话说代码江湖,风起云涌。唐僧师徒四人取经路上,遭遇种种代码劫难,幸得设计模式相助,方能化险为夷...
诗曰:
代码江湖多风波,资源泄露如妖魔。
若问如何降此患,单例模式显神通。
📖 故事
话说唐僧师徒行至"数据库连接山",只见漫山遍野皆是断裂的连接,哀鸿遍野。
唐僧愁道:"悟空,这数据库连接为何如此之多?为师眼看内存就要爆满了。"
悟空火眼金睛一扫,笑道:"师父莫慌!此乃'资源泄露妖'作祟。徒儿这就施展单例模式,叫它现出原形!"
🐉 踩坑打怪:资源泄露妖的三重劫难
第一劫:千手观音劫
师徒四人刚入山,便见一妖怪,名曰"千手观音"。此妖神通广大,每秒钟能生出千百个数据库连接!
java
// 妖怪作法:每次调用都 new 一个新连接
public class DatabaseConnection {
private static DatabaseConnection instance;
public static DatabaseConnection getInstance() {
instance = new DatabaseConnection(); // 每次都新建!
return instance;
}
}
沙僧惊呼:"大师兄!这妖怪每调用一次 getInstance,就生出一个新连接!咱们才走了十步,已经生出一万个连接了!"
唐僧脸色煞白:"一万...一万个连接?那内存..."
话音未落,只见服务器"轰"的一声,内存溢出,白龙马当场晕倒!
悟空分析:
"这妖怪的破绽在于------它根本没有保存实例!每次调用都 new 一个新的,难怪内存爆炸!"
第二劫:多线程围剿
悟空一棒子打下,千手观音倒地。谁知又冒出一妖,名曰"多线程魔王"!
此妖更加狡猾,知道单例要保存实例,便使出"并发攻击"!
java
// 懒汉式 - 看似没问题,实则暗藏杀机
public class DatabaseConnection {
private static DatabaseConnection instance;
private DatabaseConnection() {}
public static DatabaseConnection getInstance() {
if (instance == null) { // 线程 A 检查到这里
// 线程 B 也检查到这里!
instance = new DatabaseConnection(); // 两个线程都执行了!
}
return instance;
}
}
八戒看得迷糊:"猴哥,这不挺好吗?按需创建,节省资源!"
悟空喝道:"呆子!你且看仔细了!"
只见战场上,两个线程同时调用 getInstance():
- 线程 A 检查
instance == null→ 是! - 线程 B 也检查
instance == null→ 也是! - 线程 A 创建实例
- 线程 B 也创建实例!
- 两个实例! 单例破功!
八戒恍然大悟: "原来如此!两个线程同时进来,都以为自己是第一个,结果生了俩!"
第三劫:序列化与反射的双重偷袭
好不容易降服多线程魔王,又来了两位刺客------"序列化大盗"和"反射杀手"!
序列化大盗的偷袭:
java
// 即使你做了线程安全,序列化也能破坏单例!
DatabaseConnection original = DatabaseConnection.getInstance();
// 序列化到文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.ser"));
oos.writeObject(original);
// 反序列化回来
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.ser"));
DatabaseConnection deserialized = (DatabaseConnection) ois.readObject();
// 完蛋!这是另一个实例!
System.out.println(original == deserialized); // false ❌
反射杀手的绝杀:
java
// 反射可以强行调用私有构造函数!
Constructor<DatabaseConnection> constructor = DatabaseConnection.class.getDeclaredConstructor();
constructor.setAccessible(true); // 无视私有!
DatabaseConnection hacked = constructor.newInstance(); // 又生了一个!
悟空怒道:"好狠的招式!竟敢破俺老孙的单例大法!"
⚔️ 单例大法(正确姿势)
第一式:饿汉式(天生就一个)
java
public class DatabaseConnection {
private static final DatabaseConnection INSTANCE = new DatabaseConnection();
private DatabaseConnection() {
// 私有构造,不让外面随便 new
// 防反射:第二次调用直接抛异常
if (INSTANCE != null) {
throw new RuntimeException("单例类不能创建多个实例!");
}
}
public static DatabaseConnection getInstance() {
return INSTANCE; // 永远只有一个
}
// 防序列化破坏
private Object readResolve() {
return INSTANCE; // 反序列化时返回原实例
}
}
优点: 线程安全,简单粗暴
缺点: 类加载就创建,可能浪费资源
第二式:懒汉式(双重检查锁)
java
public class DatabaseConnection {
private static volatile DatabaseConnection instance;
private DatabaseConnection() {
if (instance != null) {
throw new RuntimeException("单例类不能创建多个实例!");
}
}
public static DatabaseConnection getInstance() {
if (instance == null) { // 第一次检查(避免不必要的同步)
synchronized (DatabaseConnection.class) {
if (instance == null) { // 第二次检查(确保线程安全)
instance = new DatabaseConnection();
}
}
}
return instance;
}
private Object readResolve() {
return instance;
}
}
悟空点评: "注意那个 volatile!没有它,指令重排会让其他线程拿到半成品的实例!"
第三式:静态内部类(推荐)
java
public class DatabaseConnection {
private DatabaseConnection() {
// 防反射
}
private static class Holder {
private static final DatabaseConnection INSTANCE = new DatabaseConnection();
}
public static DatabaseConnection getInstance() {
return Holder.INSTANCE; // 懒加载 + 线程安全
}
private Object readResolve() {
return Holder.INSTANCE;
}
}
八戒问: "猴哥,这为啥又懒加载又线程安全?"
悟空答: "JVM 保证!类加载是线程安全的,Holder 类只在第一次调用 getInstance 时才加载!"
第四式:枚举式(最简洁,终极形态)
java
public enum DatabaseConnection {
INSTANCE;
public void connect() {
// 连接逻辑
}
public void query(String sql) {
// 查询逻辑
}
}
悟空盛赞: "此乃单例的终极形态!天然防反射、防序列化,代码还最简洁!"
🏆 战斗总结
| 妖怪 | 攻击方式 | 破解招式 |
|---|---|---|
| 千手观音 | 每次 new 新实例 | 保存唯一实例 |
| 多线程魔王 | 并发创建 | 双重检查锁/静态内部类 |
| 序列化大盗 | 反序列化破坏 | readResolve 方法 |
| 反射杀手 | 强行调用构造 | 构造内检查/枚举 |
本章要点
| 要点 | 说明 |
|---|---|
| 核心思想 | 一个类只有一个实例 |
| 适用场景 | 资源管理、配置中心、缓存、线程池等 |
| 实现方式 | 饿汉、懒汉、静态内部类、枚举 |
| 注意事项 | 线程安全、序列化破坏、反射攻击 |
八戒的教训
八戒嘟囔道:"猴哥,那我为啥不能 new 一个?"
悟空一棒子敲过去:"呆子!你要是能随便 new,那还叫单例吗?全世界都是数据库连接,内存不爆才怪!"
八戒捂着头:"知道了知道了...单例类,私有构造,全局唯一!"
📚 系列文章
- 第一回:单例模式显神通 悟空巧解资源劫(本文)
- 第二回:工厂模式开宝店 八戒误入创建坑
- 第三回:策略模式换法宝 三打白骨精变招
- 第四回:观察者模式传音讯 千里眼顺风耳现世
- 第五回:装饰者模式添法力 悟空披挂新战袍
- 第六回:代理模式设关卡 真假美猴王难辨
- 第七回:责任链模式过难关 通关文牒层层批
- 第八回:适配器模式通万国 女儿国语言无障碍
- 第九回:外观模式简化繁 如来神掌一指定
- 第十回:模板方法定规矩 天条戒律不可违