单例模式:程序界的"独裁者"是怎样炼成的?

68 阅读3分钟

一、什么是单例模式?——"朕即国家"

想象你玩《星际争霸》:

  • 普通类:狂造兵营,爆一堆机枪兵(费钱费资源)
  • 单例类:整个地图就一个指挥中心(省钱又高效)

单例模式就是这个"指挥中心",保证整个程序"江山一统",绝不出现"军阀割据"!

二、五种实现方式——"登基大典的不同姿势"

1. 饿汉式:出生就当皇帝 👶

public class Emperor {
    private static final Emperor INSTANCE = new Emperor(); // 出生就是皇帝
    private Emperor() {} // 禁止民间私自称帝
    public static Emperor getInstance() {
        return INSTANCE; // 朕在这里!
    }
}

特点:简单粗暴,但可能"占着茅坑不拉屎"

2. 懒汉式:临时竞选皇帝 🗳️

public class LazyEmperor {
    private static LazyEmperor instance;
    private LazyEmperor() {}
    
    public static synchronized LazyEmperor getInstance() {
        if (instance == null) { // 还没皇帝?
            instance = new LazyEmperor(); // 现选一个
        }
        return instance;
    }
}

缺点:每次上朝都要"投票"(同步锁),效率低下

3. 双重检查锁:智能选举 🤖

public class SmartEmperor {
    private volatile static SmartEmperor instance;
    
    public static SmartEmperor getInstance() {
        if (instance == null) {          // 第一次微服私访
            synchronized(SmartEmperor.class) {
                if (instance == null) {  // 第二次确认
                    instance = new SmartEmperor();
                }
            }
        }
        return instance;
    }
}

妙处:既保证唯一性,又不用每次都锁整个皇宫

4. 静态内部类:世袭制 👑

public class InheritEmperor {
    private static class Heir {
        private static final InheritEmperor INSTANCE = new InheritEmperor();
    }
    
    public static InheritEmperor getInstance() {
        return Heir.INSTANCE; // 太子自动继位
    }
}

优势:天然线程安全,用时才登基

5. 枚举式:天道法则 🌌

public enum UniverseEmperor {
    INSTANCE; // 宇宙法则规定的唯一存在
    
    public void rule() {
        System.out.println("我就是天道!");
    }
}

最强王者

  • 防反射(禁止"造反")
  • 防克隆(禁止"复制人")
  • 自动序列化("龙气"永存)

三、使用场景——什么时候需要"独裁"?

  1. 玉玺(配置信息):全国只能有一个
  2. 国库(缓存):必须统一管理
  3. 禁军(线程池):调兵遣令要集中
  4. 史官(日志):历史记录必须一致

四、三大翻车现场 🚨

1. 反射攻击——"有人伪造圣旨"

Constructor<Emperor> constructor = Emperor.class.getDeclaredConstructor();
constructor.setAccessible(true);
Emperor fakeEmperor = constructor.newInstance(); // 伪帝!

防御措施

private Emperor() {
    if (INSTANCE != null) {
        throw new RuntimeException("大胆!竟敢谋反!");
    }
}

2. 序列化漏洞——"玉玺被复制"

ObjectOutputStream oos = new ObjectOutputStream(...);
oos.writeObject(emperor); // 把皇帝序列化了

ObjectInputStream ois = new ObjectInputStream(...);
Emperor cloned = (Emperor) ois.readObject(); // 克隆皇帝!

解决之道

// 添加readResolve方法
private Object readResolve() {
    return getInstance(); // 永远返回真命天子
}

3. 多线程叛乱——"八王之乱"

// 如果懒汉式没加锁...
Thread t1 = new Thread(() -> Emperor.getInstance());
Thread t2 = new Thread(() -> Emperor.getInstance());
// 可能产生两个皇帝!

五、单例模式 vs 静态类 —— "皇帝"vs"议会制"

单例模式(皇帝)静态类(议会)
继承可以传位给太子没有继承关系
状态有国库可以存钱只有法律条文(无状态)
灵活可以微服私访(实现接口)死板的法律条文
测试容易假装皇帝(Mock)很难修改法律

六、最佳实践——"明君养成手册"

  1. 优先用枚举:天道最大,省心省力
  2. Spring环境下:直接用@Service,让IOC容器当"内阁"
  3. 需要参数时:选静态内部类实现
  4. 切记:单例不是万能药,滥用会导致:
    • 代码难以测试(皇帝太多管不过来)
    • 内存泄漏(国库只进不出)

结语

单例模式就像程序世界的"帝王术":

  • 用得好:四海升平,资源统一
  • 用不好:民怨沸腾,bug丛生

记住编程界的"君主立宪制": 该独裁时独裁(如配置管理),该民主时民主(普通对象)