资讯动态

11-A-享元模式详解

发布时间:2026/10/18 1:56:40 来源:尧图企业网站定制
11-A-享元模式详解关键词享元、Flyweight、对象池、内部状态、外部状态、Integer 缓存、规格字典享元模式解决一个问题大量相似对象占内存太多把可共享的部分抽出来复用。一亿个商品 SKU 都要引用颜色曜石黑这个规格对象没必要一亿份——共享一份把每个 SKU 不同的部分留在外面传参。 术语速查表术语人话解释享元Flyweight被共享的细粒度对象内部状态可以共享、不随场景变化的状态如规格名曜石黑——存在享元里外部状态随场景变化、不能共享的状态如某个 SKU 的价格——调用时传参享元工厂/池管理共享实例的工厂通常ConcurrentHashMap.computeIfAbsent对象池享元的工程化近亲复用昂贵对象连接、线程一、不用享元的问题电商商品的规格体系每个 SKU 由多个规格项组成颜色、尺寸、版本。系统里有 500 万 SKU// ❌ 每个 SKU 都 new 一套规格对象publicclassSku{privateSpecItemcolor;// new SpecItem(颜色, 曜石黑, #000000, ...)privateSpecItemsize;// new SpecItem(尺寸, 256G, ...)privateSpecItemversion;// new SpecItem(版本, 国行, ...)privateBigDecimalprice;}全站其实只有 200 种颜色、50 种尺寸、10 种版本——但内存里躺着 1500 万个SpecItem对象。每个对象 64 字节的话就是近 1GB而实际只需要 260 个。二、模式结构内部状态进享元外部状态传参computeIfAbsent 复用computeIfAbsent 复用引用引用引用SpecItemFactory 享元池ConcurrentHashMapSpecItem 曜石黑共享一份SpecItem 256G共享一份SKU-1price 5999SKU-2price 6299拆分原则内部状态规格名、色值、排序权重所有 SKU 一样 → 存进享元共享外部状态这个 SKU 的价格、库存每个 SKU 不同 → 留在 SKU 里或调用时传参。三、业务场景商品规格字典共享享元对象不可变是共享的前提/** 规格项享元全字段 final——不可变对象才能安全共享 */publicfinalclassSpecItem{privatefinalStringspecName;// 内部状态规格名如颜色privatefinalStringspecValue;// 内部状态规格值如曜石黑privatefinalStringcolorCode;// 内部状态色值privatefinalintsortOrder;// 内部状态展示排序SpecItem(StringspecName,StringspecValue,StringcolorCode,intsortOrder){this.specNamespecName;this.specValuespecValue;this.colorCodecolorCode;this.sortOrdersortOrder;}/** 外部状态通过参数传入不存在享元里 */publicStringrenderDisplay(BigDecimalskuPrice){returnspecName: specValueskuPrice;}// 只有 getter没有 setterpublicStringgetSpecValue(){returnspecValue;}}享元工厂ComponentpublicclassSpecItemFactory{/** 享元池key 规格名 规格值 */privatefinalConcurrentHashMapString,SpecItempoolnewConcurrentHashMap();publicSpecItemget(StringspecName,StringspecValue){StringkeyspecName:specValue;// computeIfAbsent 保证并发下同一 key 只创建一次returnpool.computeIfAbsent(key,k-loadFromDb(specName,specValue));}privateSpecItemloadFromDb(StringspecName,StringspecValue){// 从规格字典表加载颜色/色值/排序SpecDictdictspecDictDao.find(specName,specValue);returnnewSpecItem(specName,specValue,dict.getColorCode(),dict.getSortOrder());}/** 池大小监控暴露给 Prometheus防止 key 设计错误导致池无限膨胀 */publicintpoolSize(){returnpool.size();}}使用方publicclassSkuAssembler{privatefinalSpecItemFactoryspecItemFactory;publicSkuassemble(SkuRawDataraw){SkuskunewSku();// 500 万个 SKU 共享 260 个 SpecItem 实例sku.setColor(specItemFactory.get(颜色,raw.getColor()));sku.setSize(specItemFactory.get(尺寸,raw.getSize()));sku.setPrice(raw.getPrice());// 外部状态留在 SKU 自己身上returnsku;}}效果1500 万个SpecItem→ 260 个内存从近 1GB 降到 KB 级。四、JDK 里最熟悉的享元Integer 缓存Integera127,b127;System.out.println(ab);// true —— 共享了缓存池里的同一个对象Integerc128,d128;System.out.println(cd);// false —— 超出缓存范围各自 newInteger.valueOf()内部就是享元工厂IntegerCache预创建 -128~127上界可用-XX:AutoBoxCacheHigh调。这也是锁Long skuId可能锁错对象的根源见 02-01-B-JMM与volatile-synchronized面试与生产事故实战——两个 127 是同一个对象两个 128 不是。同类实现Long/Short/Byte/Character缓存、String常量池、Boolean.valueOf()。五、享元 vs 对象池 vs 单例概念共享什么生命周期典型例子享元不可变的细粒度对象用完不还长期驻留池里Integer 缓存、规格字典对象池昂贵的可变资源借了要还借出→使用→归还复用数据库连接池、线程池单例全局唯一的服务对象全局一份配置客户端关键区别享元的对象是大家同时用因为不可变所以安全连接池的连接是同一时刻只能一个人用可变所以要独占借还。六、框架中的同款实现框架应用说明JDKInteger.valueOf()、String 常量池最经典的享元NettyPooledByteBufAllocator内存块复用享元对象池的混合体数据库连接池HikariCP对象池形态借还模型游戏引擎子弹/粒子对象池高频创建销毁的对象复用Caffeine/Guava Cache本地缓存本身“缓存即享元池”——很多享元场景直接用 Caffeine 实现带容量上限和过期七、常见误用与生产事故误用一共享了可变对象// ❌ SpecItem 有 setter还被共享——A 业务改了 colorCodeB 业务看到的也变了publicclassSpecItem{privateStringcolorCode;publicvoidsetColorCode(Stringc){this.colorCodec;}}享元的铁律必须不可变全 final 无 setter 集合字段防御性拷贝。做不到不可变就不要共享。误用二池的 key 里混入了外部状态// ❌ key 带了价格——每个价格一个实例池退化成每个 SKU 一份享元失效pool.computeIfAbsent(specName:specValue:price,...);key 只能由内部状态组成。判断方法如果 key 的某个组成部分每个调用方都不一样它就是外部状态不该进 key。事故享元池无上限导致内存泄漏某系统用享元缓存用户收货地址解析结果key 是完整地址字符串——地址是无限基数每个用户都不一样池只增不减运行两周后 Old 区被 2000 万个享元占满频繁 Full GC 后 OOM。修复享元只适合有限基数的字典类数据规格/地区码/配置模板无限基数的数据不配进享元池必须带上限用 Caffeine 替代裸ConcurrentHashMapmaximumSize(10_000)expireAfterAccess池大小打点监控超过预期告警——任何只进不出的 Map 都是内存泄漏嫌疑人同款问题见 01-06-A-JVM排查实战详解。八、总结享元的核心内部状态可共享存享元外部状态因人而异传参数——用共享换内存。两个前提对象数量巨大且大部分状态可共享享元必须不可变。实现三件套不可变享元类 computeIfAbsent享元工厂 池大小监控。适用边界有限基数的字典类数据规格/地区/模板无限基数数据进池 内存泄漏宁可用带淘汰的 Caffeine。辨析享元是大家同时用不可变对象对象池是排队借还可变资源别混为一谈。上一篇10-A-外观模式详解下一篇12-A-代理模式详解

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑