Java 23 种设计模式:从踩坑到精通 | 番外:享元 vs 单例 —— 全局唯一 vs 按种类唯一

摘要:单例模式和享元模式都能减少对象创建、节约内存,但单例保证的是“全类唯一实例”,享元实现的是“每种内部状态一个共享实例”。本文从文本编辑器字符共享和数据库连接池两个场景出发,结合代码对比、UML 分析和 Integer.valueOf()、Spring 单例 Bean 等 JDK 源码,帮你彻底分清这对“节约资源型”模式。

🗺️ 本文阅读地图(3 分钟速览)

  • 为什么字符共享是享元,连接池不是?
  • UML 对比:全局唯一 vs 池化共享
  • 手写享元工厂 vs 单例类,看代码感受差异
  • JDK Integer.valueOf()(享元)vs Runtime(单例)源码分析
  • 面试必问:“享元和单例怎么区分?Integer.valueOf() 用了哪个?”

📖 《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 当前:番外 · 享元 vs 单例
🔗 返回系列总目录



1. 从“字符共享”和“连接池”两个场景说起

1.1 场景一:文本编辑器字符共享(享元模式)

一篇十万字的文章,如果每个字符都 new 一个 Character 对象,内存会迅速撑爆。但仔细观察会发现:英文字母只有 26 个,常用汉字也就几千。真正需要反复创建的是“字符本身”,而字体、颜色等属性是可复用的。

// 享元工厂:按内部状态缓存共享实例
public class CharacterFactory {
    private static final Map<String, TextCharacter> pool = new ConcurrentHashMap<>();

    public static TextCharacter getCharacter(char symbol, String font, int size) {
        String key = symbol + "_" + font + "_" + size;
        return pool.computeIfAbsent(key, k -> new ConcreteCharacter(symbol, font, size));
    }
}

// 客户端:11 个字符只创建了 8 个享元实例(l 和 o 重复)
TextCharacter c1 = CharacterFactory.getCharacter('H', "Arial", 12);  // 新创建
TextCharacter c2 = CharacterFactory.getCharacter('l', "Arial", 12);  // 新创建
TextCharacter c3 = CharacterFactory.getCharacter('l', "Arial", 12);  // 复用!和 c2 是同一个对象
TextCharacter c4 = CharacterFactory.getCharacter('o', "Arial", 12);  // 新创建
TextCharacter c5 = CharacterFactory.getCharacter('o', "Arial", 12);  // 复用!和 c4 是同一个对象

💬 白话:享元工厂就像一个“字符仓库”,你要什么字符先问仓库有没有,有就直接用,没有再创建。仓库里每种字符只有一个实例。

1.2 场景二:数据库连接池 vs 全局配置(区分享元和单例)

连接池 ≠ 享元模式:连接池中的连接对象是可变的——借出时占用,归还时释放,状态随时变化。这是对象池模式,不是享元模式。

全局配置 = 单例模式:系统配置管理器全局只需一个实例,所有模块共享它读取的配置信息。

// 单例:全局唯一实例
public class AppConfig {
    private static final AppConfig INSTANCE = new AppConfig();
    private AppConfig() {}
    public static AppConfig getInstance() { return INSTANCE; }
    public String getProperty(String key) { ... }
}

// 享元:每种内部状态一个共享实例
Integer a = Integer.valueOf(100);  // 从缓存池中获取,-128~127 范围内的整数共享
Integer b = Integer.valueOf(100);
System.out.println(a == b);  // true,同一个对象!

💬 白话:单例是“全班只有一个班长”,享元是“每种笔颜色只有一支,全班共享这几种颜色的笔”。

1.3 关键差异已浮现

  • 单例:全局有且仅有一个实例,无论什么场景都返回这同一个对象;
  • 享元:每种内部状态对应一个共享实例,享元池中可以有多个不同内部状态的对象。

2. UML 对比

在这里插入图片描述


3. 三大核心区别

对比维度 单例模式 享元模式
实例数量 全局唯一(1 个) 每种内部状态一个(N 个)
共享方式 所有客户端共享同一个实例 相同内部状态的客户端共享一个实例
对象可变性 通常持有可变全局状态 必须不可变(内部状态不可修改)
外部状态 无外部状态概念 外部状态由客户端传入和管理
典型应用 Spring 单例 Bean、Runtime、日志工厂 Integer.valueOf(-128~127)、字符串常量池

4. JDK 源码深度对比

4.1 Integer.valueOf()(享元模式)

// Integer 对 -128~127 范围的整数进行了缓存
Integer a = Integer.valueOf(100);   // 从缓存中获取
Integer b = Integer.valueOf(100);   // 从缓存中获取,同一个对象
System.out.println(a == b);         // true,享元生效

Integer c = Integer.valueOf(200);   // 超出缓存范围,新建对象
Integer d = Integer.valueOf(200);   // 新建对象
System.out.println(c == d);         // false,不在缓存范围内

为什么是享元?

  • 每种内部状态(-128~127 的整数值)对应一个共享实例;
  • 享元池中有 256 个对象,不是全局唯一;
  • Integer 对象不可变——这是享元模式的核心约束。

4.2 Runtime(单例模式)

// Runtime 是典型的饿汉式单例
Runtime runtime1 = Runtime.getRuntime();
Runtime runtime2 = Runtime.getRuntime();
System.out.println(runtime1 == runtime2);  // true,全局唯一

为什么是单例?

  • 整个 JVM 中只有一个 Runtime 实例;
  • 构造器私有,只提供静态方法 getRuntime()
  • Runtime 代表整个 Java 运行时环境,天然需要全局唯一。

5. 什么时候不该用?

滥用场景 应该用什么 原因
对象状态需要频繁修改 对象池或直接 new 享元要求对象不可变,状态可变会导致数据污染
只有 2~3 个对象,内存不是问题 直接 new 享元工厂增加代码复杂度,收益不大
需要全局唯一的控制点 单例 享元可以创建多个实例,不是全局唯一
连接池、线程池 对象池模式 这些对象是有状态、可变的,不是享元

6.面试必问 + 追问连环炮

基础必问

  • 享元和单例有什么区别? → 单例全局只有一个实例,享元每种内部状态一个共享实例。享元池中可以有多个不同内部状态的对象。
  • Integer.valueOf() 用了什么模式? → 享元模式,缓存 -128~127 的整数对象。

面试官追问

  • “为什么数据库连接池不是享元模式?”
    👉 连接池中的连接对象是可变的(借出/归还/超时),享元模式要求对象不可变。连接池是对象池模式,不是享元。
  • “享元对象必须是不可变的吗?”
    👉 必须。如果内部状态可变,A 客户端修改了共享对象,B 客户端拿到的就是脏数据。这是享元模式最致命的踩坑点。
  • “字符串常量池是享元还是单例?”
    👉 享元。每个字符串内容对应一个共享实例,常量池中可以有多个不同内容的字符串对象,不是全局唯一。

🎉 恭喜:如果你能立刻说出 Integer.valueOf() 是享元模式(每种值一个共享实例),Runtime 是单例模式(全局唯一),并理解“享元对象必须不可变”的原因,你已经掌握了这对“节约资源型”模式的核心区分点。

💡 延伸阅读:本文是享元模式与单例模式的深度对比。如果你想先了解每种模式的完整原理、UML、代码实现和面试题,可以先阅读正篇——享元模式:内存吃不消?试试共享细粒度对象单例模式:你写的单例真的安全吗?,再回来看这篇对比,效果更佳。

🧭 《Java 23 种设计模式:从踩坑到精通》快速导航

🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。

📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐