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

📅 发布时间:2026/7/15 22:29:59
Java 23 种设计模式:从踩坑到精通 | 番外:享元 vs 单例 —— 全局唯一 vs 按种类唯一 Java 23 种设计模式从踩坑到精通 | 番外享元 vs 单例 —— 全局唯一 vs 按种类唯一摘要单例模式和享元模式都能减少对象创建、节约内存但单例保证的是“全类唯一实例”享元实现的是“每种内部状态一个共享实例”。本文从文本编辑器字符共享和数据库连接池两个场景出发结合代码对比、UML 分析和Integer.valueOf()、Spring 单例 Bean 等 JDK 源码帮你彻底分清这对“节约资源型”模式。️本文阅读地图3 分钟速览为什么字符共享是享元连接池不是UML 对比全局唯一 vs 池化共享手写享元工厂 vs 单例类看代码感受差异JDKInteger.valueOf()享元vsRuntime单例源码分析面试必问“享元和单例怎么区分Integer.valueOf()用了哪个”《Java 23 种设计模式从踩坑到精通》开篇系列介绍与目录 当前番外 · 享元 vs 单例 返回系列总目录文章目录Java 23 种设计模式从踩坑到精通 | 番外享元 vs 单例 —— 全局唯一 vs 按种类唯一1. 从“字符共享”和“连接池”两个场景说起1.1 场景一文本编辑器字符共享享元模式1.2 场景二数据库连接池 vs 全局配置区分享元和单例1.3 关键差异已浮现2. UML 对比3. 三大核心区别4. JDK 源码深度对比4.1 Integer.valueOf()享元模式4.2 Runtime单例模式5. 什么时候不该用6.面试必问 追问连环炮 《Java 23 种设计模式从踩坑到精通》快速导航1. 从“字符共享”和“连接池”两个场景说起1.1 场景一文本编辑器字符共享享元模式一篇十万字的文章如果每个字符都new一个Character对象内存会迅速撑爆。但仔细观察会发现英文字母只有 26 个常用汉字也就几千。真正需要反复创建的是“字符本身”而字体、颜色等属性是可复用的。// 享元工厂按内部状态缓存共享实例publicclassCharacterFactory{privatestaticfinalMapString,TextCharacterpoolnewConcurrentHashMap();publicstaticTextCharactergetCharacter(charsymbol,Stringfont,intsize){Stringkeysymbol_font_size;returnpool.computeIfAbsent(key,k-newConcreteCharacter(symbol,font,size));}}// 客户端11 个字符只创建了 8 个享元实例l 和 o 重复TextCharacterc1CharacterFactory.getCharacter(H,Arial,12);// 新创建TextCharacterc2CharacterFactory.getCharacter(l,Arial,12);// 新创建TextCharacterc3CharacterFactory.getCharacter(l,Arial,12);// 复用和 c2 是同一个对象TextCharacterc4CharacterFactory.getCharacter(o,Arial,12);// 新创建TextCharacterc5CharacterFactory.getCharacter(o,Arial,12);// 复用和 c4 是同一个对象白话享元工厂就像一个“字符仓库”你要什么字符先问仓库有没有有就直接用没有再创建。仓库里每种字符只有一个实例。1.2 场景二数据库连接池 vs 全局配置区分享元和单例连接池 ≠ 享元模式连接池中的连接对象是可变的——借出时占用归还时释放状态随时变化。这是对象池模式不是享元模式。全局配置 单例模式系统配置管理器全局只需一个实例所有模块共享它读取的配置信息。// 单例全局唯一实例publicclassAppConfig{privatestaticfinalAppConfigINSTANCEnewAppConfig();privateAppConfig(){}publicstaticAppConfiggetInstance(){returnINSTANCE;}publicStringgetProperty(Stringkey){...}}// 享元每种内部状态一个共享实例IntegeraInteger.valueOf(100);// 从缓存池中获取-128~127 范围内的整数共享IntegerbInteger.valueOf(100);System.out.println(ab);// true同一个对象白话单例是“全班只有一个班长”享元是“每种笔颜色只有一支全班共享这几种颜色的笔”。1.3 关键差异已浮现单例全局有且仅有一个实例无论什么场景都返回这同一个对象享元每种内部状态对应一个共享实例享元池中可以有多个不同内部状态的对象。2. UML 对比3. 三大核心区别对比维度单例模式享元模式实例数量全局唯一1 个每种内部状态一个N 个共享方式所有客户端共享同一个实例相同内部状态的客户端共享一个实例对象可变性通常持有可变全局状态必须不可变内部状态不可修改外部状态无外部状态概念外部状态由客户端传入和管理典型应用Spring 单例 Bean、Runtime、日志工厂Integer.valueOf(-128~127)、字符串常量池4. JDK 源码深度对比4.1Integer.valueOf()享元模式// Integer 对 -128~127 范围的整数进行了缓存IntegeraInteger.valueOf(100);// 从缓存中获取IntegerbInteger.valueOf(100);// 从缓存中获取同一个对象System.out.println(ab);// true享元生效IntegercInteger.valueOf(200);// 超出缓存范围新建对象IntegerdInteger.valueOf(200);// 新建对象System.out.println(cd);// false不在缓存范围内为什么是享元每种内部状态-128~127 的整数值对应一个共享实例享元池中有 256 个对象不是全局唯一Integer对象不可变——这是享元模式的核心约束。4.2Runtime单例模式// Runtime 是典型的饿汉式单例Runtimeruntime1Runtime.getRuntime();Runtimeruntime2Runtime.getRuntime();System.out.println(runtime1runtime2);// true全局唯一为什么是单例整个 JVM 中只有一个Runtime实例构造器私有只提供静态方法getRuntime()Runtime代表整个 Java 运行时环境天然需要全局唯一。5. 什么时候不该用滥用场景应该用什么原因对象状态需要频繁修改对象池或直接new享元要求对象不可变状态可变会导致数据污染只有 2~3 个对象内存不是问题直接new享元工厂增加代码复杂度收益不大需要全局唯一的控制点单例享元可以创建多个实例不是全局唯一连接池、线程池对象池模式这些对象是有状态、可变的不是享元6.面试必问 追问连环炮基础必问享元和单例有什么区别→ 单例全局只有一个实例享元每种内部状态一个共享实例。享元池中可以有多个不同内部状态的对象。Integer.valueOf()用了什么模式→ 享元模式缓存 -128~127 的整数对象。面试官追问“为什么数据库连接池不是享元模式” 连接池中的连接对象是可变的借出/归还/超时享元模式要求对象不可变。连接池是对象池模式不是享元。“享元对象必须是不可变的吗”必须。如果内部状态可变A 客户端修改了共享对象B 客户端拿到的就是脏数据。这是享元模式最致命的踩坑点。“字符串常量池是享元还是单例” 享元。每个字符串内容对应一个共享实例常量池中可以有多个不同内容的字符串对象不是全局唯一。恭喜如果你能立刻说出Integer.valueOf()是享元模式每种值一个共享实例Runtime是单例模式全局唯一并理解“享元对象必须不可变”的原因你已经掌握了这对“节约资源型”模式的核心区分点。延伸阅读本文是享元模式与单例模式的深度对比。如果你想先了解每种模式的完整原理、UML、代码实现和面试题可以先阅读正篇——享元模式内存吃不消试试共享细粒度对象 和 单例模式你写的单例真的安全吗再回来看这篇对比效果更佳。 《Java 23 种设计模式从踩坑到精通》快速导航开篇系列介绍与目录当前番外 · 享元 vs 单例 —— 全局唯一 vs 按种类唯一你在这里 延伸阅读享元模式正篇 单例模式正篇创建型模式汇总结构型模式汇总行为型模式汇总 关注《Java 23 种设计模式从踩坑到精通》用 25 篇文章彻底吃透设计模式。福利预告全系列代码及 UML 源码将在完结时统一打包开放点击「关注」「收藏」第一时间获取。 除了设计模式我也在深挖智能物流实战WMS、托盘调度、机器学习落地。欢迎点击头像看看专栏 《出版社物流WMS智能调度实战》。技术相通思路可鉴。