当代码开始「单身」:解密Java单例模式的千层套路

74 阅读3分钟

当代码开始「单身」:解密Java单例模式的千层套路


一、欢迎来到「全球唯一」俱乐部

想象你要开发一个「限量版手办发售系统」,全球仅允许存在唯一的库存管理器。这时如果new出多个对象,程序员将面临比双十一秒杀更惨烈的灾难——这就是单例模式的专属舞台。

官方解释
单例模式(Singleton)确保一个类只有一个实例,并提供全局访问入口。就像公司茶水间的咖啡机——全楼层共享,但绝对禁止私自克隆。


二、单例的「身份证照片」(UML图)

   ┌─────────────┐
   │  Singleton  │
   ├─────────────┤
   │ -instance   │
   ├─────────────┤
   │ +getInstance│
   │ -Singleton()│
   └─────────────┘
  • 唯一保安(-instance):持枪守卫类的唯一实例
  • 安检通道(+getInstance):全球统一入口
  • 私人领地(私有构造器):禁止私自new对象

三、单身狗的N种活法(代码实现)

1. 饿汉式:程序启动就开吃
public class HungerSingleton {
    // 程序启动立即加载(像极了你囤的泡面)
    private static final HungerSingleton instance = new HungerSingleton();
    
    private HungerSingleton() {} // 锁死构造器
    
    public static HungerSingleton 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 DoubleCheckSingleton {
    private static volatile DoubleCheckSingleton instance;
    
    private DoubleCheckSingleton() {}
    
    public static DoubleCheckSingleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) { // 第二次检查
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}
  • volatile:防止指令重排序(禁止JVM搞小动作)
  • 双重if判断:避免无谓的锁竞争

4. 静态内部类:优雅的延迟加载
public class InnerClassSingleton {
    private InnerClassSingleton() {}
    
    // 当访问Holder时才会加载(魔法触发点)
    private static class Holder {
        static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance() {
        return Holder.INSTANCE; // 优雅取餐
    }
}

优点:兼顾延迟加载与线程安全,写法优雅


5. 枚举式:Java官方认证的黄金方案
public enum EnumSingleton {
    INSTANCE; // 大写字母宣告主权
    
    public void doSomething() {
        System.out.println("我天生免疫反射攻击!");
    }
}
  • 自带序列化安全
  • 天然防反射破坏
  • 代码极简主义

四、单身选秀大赛(方案对比)

方案线程安全延迟加载防反射代码复杂度
饿汉式
懒汉式⭐⭐
双重检查锁⭐⭐⭐
静态内部类⭐⭐
枚举式

选美建议

  • 简单场景 → 饿汉式
  • 严格要求延迟加载 → 静态内部类
  • 需要防反射 → 枚举式

五、现实世界的「唯一」艺术

  1. 日志管理器:全系统共享一个日志写入器
  2. 配置读取:避免重复解析配置文件
  3. 数据库连接池:统一管理珍贵连接资源
  4. GUI组件:全局唯一的对话框管理器
  5. 缓存系统:中央缓存指挥官

经典案例
Spring框架的Bean默认采用单例模式管理,但不同于传统实现,而是通过IoC容器控制


六、防翻车指南(最佳实践)

  1. 线程安全是底线:多线程环境必须加锁
  2. 序列化要小心:重写readResolve()方法
  3. 反射攻击防护:构造器中添加防御代码
  4. 避免过度使用:单例不是银弹,滥用会导致代码僵化
  5. 现代框架优先:尽量使用Spring等框架管理单例

防反射彩蛋

private Singleton() {
    if (instance != null) {
        throw new IllegalStateException("想用反射破解?没门!");
    }
}

七、单身总结会

单例模式就像代码界的「灭霸手套」——拥有独一无二的力量,但使用不当可能引发灾难。选择实现方案时,要像挑选约会对象般谨慎:

  • :保证全局唯一性
  • :注意线程安全
  • 不要:为了单例而单例
  • 不要:忽视现代框架的解决方案

当你下次看到Runtime.getRuntime()这样的代码时,请对它露出会心一笑——这可是Java标准库的官方认证单身汉!