首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

享元模式:如何通过对象共享实现亿级系统的内存优化

  • 25-04-18 16:40
  • 3650
  • 5235
juejin.cn

享元模式:如何通过对象共享实现亿级系统的内存优化

一、模式核心:用共享对象破解内存膨胀难题

在电商系统中,若每个商品规格(如「红色 / L 码 T 恤」「蓝色 / XL 码卫衣」)都创建独立对象,当 SKU 数量达到百万级时,内存占用将急剧飙升。享元模式(Flyweight Pattern)通过共享细粒度对象,将重复对象的内存开销降低 90% 以上,其核心思想是:

  • 对象复用:缓存重复对象,避免重复创建
  • 状态分离:将不可变的「内部状态」(如商品基础属性)共享,可变的「外部状态」(如库存、价格)由客户端传入

核心角色与 UML 类图

角色职责示例(商品规格场景)
享元接口定义共享对象的公共接口,支持传入外部状态ProductSpec接口
具体享元实现享元接口,封装内部状态,外部状态通过参数传入ClothingSpec具体实现类
享元工厂管理享元对象的缓存池,确保相同内部状态的对象被共享ProductSpecFactory工厂类
客户端通过享元工厂获取享元对象,并传入外部状态进行操作商品库存管理模块
plantuml
代码解读
复制代码
@startuml interface Flyweight { void operate(String externalState); } class ConcreteFlyweight implements Flyweight { private String intrinsicState; ConcreteFlyweight(String intrinsicState) {this.intrinsicState = intrinsicState;} void operate(String externalState) { /* 处理内外状态 */ } } class FlyweightFactory { private Map pool = new HashMap<>(); Flyweight getFlyweight(String key) { if (!pool.containsKey(key)) { pool.put(key, new ConcreteFlyweight(key)); } return pool.get(key); } } class Client { public static void main(String[] args) { FlyweightFactory factory = new FlyweightFactory(); Flyweight fw1 = factory.getFlyweight("红色/L码"); Flyweight fw2 = factory.getFlyweight("红色/L码"); System.out.println(fw1 == fw2); // 输出true(对象共享) } } @enduml

二、手把手实现线程安全的享元工厂

1. 定义享元接口(内部状态抽象)

java
代码解读
复制代码
public interface ProductSpec { // 外部状态通过参数传入,如实时库存、促销价 void displayStockInfo(int stockCount, double discountPrice); }

2. 实现具体享元(封装不可变的内部状态)

java
代码解读
复制代码
public class ConcreteProductSpec implements ProductSpec { private final String specId; // 规格ID(内部状态:不可变) private final String productName; // 商品名称(内部状态:不可变) private final String color; // 颜色(内部状态:不可变) private final String size; // 尺码(内部状态:不可变) public ConcreteProductSpec(String specId, String productName, String color, String size) { this.specId = specId; this.productName = productName; this.color = color; this.size = size; } @Override public void displayStockInfo(int stockCount, double discountPrice) { System.out.println("规格:" + productName + " - " + color + "/" + size + "\n库存:" + stockCount + " 折扣价:" + discountPrice + "\n对象地址:" + System.identityHashCode(this)); } }

3. 构建线程安全的享元工厂(核心缓存逻辑)

java
代码解读
复制代码
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ProductSpecFactory { // 使用线程安全的ConcurrentHashMap作为缓存池 private static final Map specPool = new ConcurrentHashMap<>(); public static ProductSpec getSpec(String specId, String productName, String color, String size) { // 生成缓存键:组合所有内部状态字段 String key = specId + "-" + productName + "-" + color + "-" + size; return specPool.computeIfAbsent(key, k -> new ConcreteProductSpec(specId, productName, color, size)); } }

4. 客户端调用与内存优化验证

java
代码解读
复制代码
public class ClientDemo { public static void main(String[] args) { // 模拟生成10万个相同规格的对象 List specList = new ArrayList<>(); for (int i = 0; i < 100000; i++) { ProductSpec spec = ProductSpecFactory.getSpec( "P001", "纯棉T恤", "红色", "L" ); spec.displayStockInfo(100 + i, 99.9 - i * 0.1); // 传入变化的外部状态 specList.add(spec); } // 验证对象共享:所有相同规格对象地址相同 System.out.println("对象总数:" + specList.size()); // 100000 System.out.println("唯一对象数:" + specPool.size()); // 1(仅缓存1个对象) } }

三、JDK 源码与框架中的享元实践

1. Integer 缓存(-128~127 的自动装箱优化)

java
代码解读
复制代码
Integer a = 100; // 调用Integer.valueOf(100),从缓存池获取对象 Integer b = 100; // a == b 返回true(对象共享) Integer c = 200; // 超过缓存范围,创建新对象 Integer d = 200; // c == d 返回false
  • 源码解析:IntegerCache类作为享元工厂,缓存常用整数值
  • 优化点:通过-XX:AutoBoxCacheMax=200可调整缓存上限

2. String 常量池(字符串字面量的共享)

java
代码解读
复制代码
String str1 = "设计模式"; // 存入常量池 String str2 = "设计模式"; // 直接引用常量池对象,str1 == str2为true String str3 = new String("设计模式"); // 创建新对象,str1 == str3为false
  • 实战技巧:通过intern()方法将动态生成的字符串加入常量池

3. 企业级案例:电商 SKU 规格管理

当系统存在 10 万 + SKU 时,传统模式需创建 10 万个独立对象(约占内存 50MB),使用享元模式后仅需缓存唯一规格对象(约占内存 5KB),内存占用降低 99%。

java
代码解读
复制代码
// 外部状态示例:不同时间的库存与价格 ProductSpec redL = ProductSpecFactory.getSpec("P001", "T恤", "红", "L"); redL.displayStockInfo(500, 99.9); // 上午10点数据 redL.displayStockInfo(300, 89.9); // 下午3点数据(复用同一对象,传入不同外部状态)

四、避坑指南:享元模式的正确打开方式

1. 必须严格区分内外状态

  • ✅ 内部状态(Immutable):对象创建后不可变,如规格 ID、基础属性
  • ❌ 错误实践:将外部状态(如库存)存入享元对象,导致线程安全问题

2. 缓存池的容量控制

  • 使用WeakHashMap避免内存泄漏(适用于非核心对象)
  • 实现 LRU 淘汰策略(当缓存过大时,移除最近最少使用的对象)
java
代码解读
复制代码
// 示例:基于LinkedHashMap实现LRU缓存 public class LRUFlyweightFactory extends LinkedHashMap { private final int MAX_CACHE_SIZE; public LRUFlyweightFactory(int maxSize) { super(maxSize + 1, 0.75f, true); MAX_CACHE_SIZE = maxSize; } @Override protected boolean removeEldestEntry(Map.Entry entry) { return size() > MAX_CACHE_SIZE; } }

3. 反模式:过度优化的陷阱

  • 当对象创建成本极低时(如简单数据类),享元模式可能增加代码复杂度
  • 避免为极少重复的对象创建缓存(如系统配置类,单例模式更合适)

五、总结:何时该用享元模式?

适用场景判断条件典型案例
对象数量巨大预计对象数超过 10 万 +,且大量重复电商 SKU、游戏道具、文档字体
内部状态可共享存在稳定不变的核心属性组合数据库连接参数、商品基础信息
外部状态可动态传入变化的属性可通过方法参数传递实时价格、库存数量

通过享元模式,我们将对象创建的粒度从「每个实例独立创建」提升到「共享核心状态 + 动态组装外部状态」,这不仅是代码层面的优化,更是对「数据复用」思想的深度实践。下一篇我们将探讨组合模式如何用树形结构管理复杂对象关系,敬请期待!

动手实践文档(附代码仓库链接)

1. 环境准备
  • JDK 1.8+
  • IDEA/Eclipse
  • Maven 依赖(可选,用于项目管理):
xml
代码解读
复制代码
<dependencies> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> <version>1.18.24version> dependency> dependencies>
2. 代码实现步骤
  1. 创建享元接口ProductSpec.java
  2. 实现具体享元ConcreteProductSpec.java
  3. 构建线程安全的工厂类ProductSpecFactory.java
  4. 编写客户端测试类ClientDemo.java
3. 关键调试点
  • 验证对象是否被共享:通过System.identityHashCode()打印对象地址
  • 监控内存变化:使用 JVisualVM 观察堆内存中ConcreteProductSpec实例数量
  • 测试多线程场景:启动 10 个线程并发调用getSpec(),验证缓存一致性
4. 扩展任务
  1. 为享元工厂添加日志功能,记录对象创建与复用次数
  2. 实现可视化缓存监控面板,实时显示缓存命中率
  3. 对比享元模式与普通模式的性能差异(建议使用 JMH 基准测试)
5. 推荐阅读
  • GoF《设计模式》原书第 8 章(享元模式详细定义)
  • Oracle 官方文档:Integer Cache Implementation
  • 深入理解 JVM:String 常量池实现原理
注:本文转载自juejin.cn的AronTing的文章"https://juejin.cn/post/7493887688829026339"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

103
后端
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top