Java 设计模式西游篇 - 第一回:单例模式显神通 悟空巧解资源劫

话说代码江湖,风起云涌。唐僧师徒四人取经路上,遭遇种种代码劫难,幸得设计模式相助,方能化险为夷...


诗曰:

复制代码
代码江湖多风波,资源泄露如妖魔。
若问如何降此患,单例模式显神通。

📖 故事

话说唐僧师徒行至"数据库连接山",只见漫山遍野皆是断裂的连接,哀鸿遍野。

唐僧愁道:"悟空,这数据库连接为何如此之多?为师眼看内存就要爆满了。"

悟空火眼金睛一扫,笑道:"师父莫慌!此乃'资源泄露妖'作祟。徒儿这就施展单例模式,叫它现出原形!"


🐉 踩坑打怪:资源泄露妖的三重劫难

第一劫:千手观音劫

师徒四人刚入山,便见一妖怪,名曰"千手观音"。此妖神通广大,每秒钟能生出千百个数据库连接!

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()

  1. 线程 A 检查 instance == null → 是!
  2. 线程 B 也检查 instance == null → 也是!
  3. 线程 A 创建实例
  4. 线程 B 也创建实例!
  5. 两个实例! 单例破功!

八戒恍然大悟: "原来如此!两个线程同时进来,都以为自己是第一个,结果生了俩!"


第三劫:序列化与反射的双重偷袭

好不容易降服多线程魔王,又来了两位刺客------"序列化大盗"和"反射杀手"!

序列化大盗的偷袭:

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,那还叫单例吗?全世界都是数据库连接,内存不爆才怪!"

八戒捂着头:"知道了知道了...单例类,私有构造,全局唯一!"


📚 系列文章

  • 第一回:单例模式显神通 悟空巧解资源劫(本文)
  • 第二回:工厂模式开宝店 八戒误入创建坑
  • 第三回:策略模式换法宝 三打白骨精变招
  • 第四回:观察者模式传音讯 千里眼顺风耳现世
  • 第五回:装饰者模式添法力 悟空披挂新战袍
  • 第六回:代理模式设关卡 真假美猴王难辨
  • 第七回:责任链模式过难关 通关文牒层层批
  • 第八回:适配器模式通万国 女儿国语言无障碍
  • 第九回:外观模式简化繁 如来神掌一指定
  • 第十回:模板方法定规矩 天条戒律不可违
相关推荐
ren049182 小时前
多线程、单例模式
java
Nuopiane2 小时前
MyPal3(7)
java·开发语言
不光头强2 小时前
object所有方法及知识点
java·开发语言·jvm
予枫的编程笔记2 小时前
【面试专栏|JVM虚拟机】CMS vs 其他垃圾收集器:核心差异+适用场景
java·jvm·java面试·后端开发·垃圾回收机制·cmv垃圾回收器·jvm性能优化
渡过晚枫2 小时前
[第十六届蓝桥杯/java/算法]1.偏蓝
java·算法·蓝桥杯
zhaoyin19942 小时前
JavaScript面试题笔记
java·javascript·笔记
计算机学姐2 小时前
基于SpringBoot的宠物诊所管理系统
java·vue.js·spring boot·后端·spring·elementui·宠物
2501_940315262 小时前
【无标题】1302 层数最深叶子节点的和
java·数据结构·算法
悟能不能悟2 小时前
idea默认的快捷键和eclipse配置快捷键对比,列出一些常用的
java·eclipse·intellij-idea