ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

大白话说Java设计模式-25-享元模式(源码剖析篇)

大白话说Java设计模式-25-享元模式(源码剖析篇) **大厂规范**Java项目工具类 — [11_时间差值计算工具类Java企业级代码](https://download.csdn.net/download/horizon90/93427153)大白话说Java设计模式-25-享元模式源码剖析篇JDK / 中间件中的享元实现一句话本质享元在源码里不是省内存的形象化而是缓存池 复用的工业级架构。️ 标签享元模式 / Integer 缓存 / String 常量池 / 数据库连接池 适合中高级后端 / 想读懂主流框架源码的工程师目录一、为什么读享元源码一张地图先看清二、JDK 源码剖析Integer 缓存三、JDK 源码剖析String 常量池四、中间件源码剖析数据库连接池五、为什么框架作者这么设计六、借鉴到我们项目3 个抄作业实践七、本篇小结 下一模式预告一、为什么读享元源码一张地图先看清享元模式在 JDK 里出现得最经典——Integer/String/Long等都有享元思想。源码地图类别框架 / 中间件享元实现缓存内容JDKjava.lang.IntegerIntegerCache-128 ~ 127JDKjava.lang.LongLongCache-128 ~ 127JDKjava.lang.StringStringTable字面量JDKjava.lang.BooleanTRUE/FALSE2 个值JDKjava.lang.Byte全范围-128 ~ 127JDKjava.lang.CharacterCharacterCache0 ~ 127数据库HikariCPConnection池数据库连接数据库DruidConnection池数据库连接线程池ThreadPoolExecutorThread池线程我们按Integer → String → 连接池的顺序来剖析。二、JDK 源码剖析Integer 缓存2.1Integer.valueOf享元工厂方法源码位置OpenJDK 17src/java.base/share/classes/java/lang/Integer.java核心源码简化版/** * ✅ 享元工厂方法 */publicstaticIntegervalueOf(inti){if(iIntegerCache.lowiIntegerCache.high){returnIntegerCache.cache[i(-IntegerCache.low)];}returnnewInteger(i);}关键点关键点解释IntegerCache.low/high享元池范围IntegerCache.cache[]享元池数组缓存内享元共享缓存外新建2.2IntegerCache内部类核心源码/** * ✅ 享元池-128 ~ 127 */privatestaticclassIntegerCache{staticfinalintlow-128;staticfinalinthigh127;staticfinalInteger[]cache;static{// ✅ 类加载时初始化享元池inth127;inti-128;cachenewInteger[(h-i)1];intjlow;for(intk0;kcache.length;k){cache[k]newInteger(j);}// high 可通过 JVM 参数调整hIntegerCache.high;}privateIntegerCache(){}}关键点关键点解释静态初始化类加载时创建 256 个 Integer范围可调-XX:AutoBoxCacheMaxN线程安全类初始化由 JVM 保证2.3 完整 Integer 享元调用Integer a 100; // 自动装箱调 valueOf(100) ↓ IntegerCache.cache[100 - (-128)] IntegerCache.cache[228] ↓ 返回共享对象2.4 享元效果演示/** * ✅ 享元效果 */publicclassIntegerFlyweightDemo{publicstaticvoidmain(String[]args){Integera100;Integerb100;System.out.println(ab);// ✅ true同一对象享元Integerc200;Integerd200;System.out.println(cd);// ❌ false不同对象超出缓存}}2.5 其他类型的享元LongCache/** * ✅ Long 缓存-128 ~ 127 */privatestaticclassLongCache{staticfinalLong[]cache;static{cachenewLong[256];for(inti0;icache.length;i){cache[i]newLong(i-128);}}}publicstaticLongvalueOf(longl){intoffset128;if(l-128l127){returnLongCache.cache[(int)loffset];}returnnewLong(l);}Boolean 享元/** * ✅ Boolean 享元只有 2 个对象 */publicstaticfinalBooleanTRUEnewBoolean(true);publicstaticfinalBooleanFALSEnewBoolean(false);publicstaticBooleanvalueOf(booleanb){return(b?TRUE:FALSE);// 永远是 2 个对象之一}Character 享元/** * ✅ Character 缓存0 ~ 127 */privatestaticclassCharacterCache{staticfinalCharacter[]cache;static{cachenewCharacter[128];for(inti0;icache.length;i){cache[i]newCharacter((char)i);}}}publicstaticCharactervalueOf(charc){if(c127){returnCharacterCache.cache[(int)c];}returnnewCharacter(c);}2.6 Integer 享元的作者选择设计点选择原因缓存范围-128 ~ 127小整数最常用可配置-XX:AutoBoxCacheMax灵活类加载初始化static blockJVM 保证线程安全共享不可变Integer 是不可变防止共享对象被修改三、JDK 源码剖析String 常量池3.1String.intern()享元方法源码位置OpenJDK 17src/java.base/share/classes/java/lang/String.java核心源码/** * ✅ 享元方法将字符串放入常量池 */publicnativeStringintern();关键点关键点解释intern()把字符串加入常量池池内字符串共享享元池外字符串新建3.2 String 常量池的实现StringTable/** * ✅ StringTable字符串常量池 * p * JDK 6 之前在 PermGenJDK 7 在堆 */StringTable(C实现)├──hello→Stringref1├──world→Stringref2└──...完整调用链String s1 hello; // 字面量先查常量池 ↓ StringTable.has(hello) ? ├── ✅ 有 → 直接返回引用 └── ❌ 无 → 创建 String加入 StringTable3.3 享元效果/** * ✅ String 享元 */Stringahello;Stringbhello;System.out.println(ab);// ✅ true同一对象常量池StringcnewString(hello);StringdnewString(hello);System.out.println(cd);// ❌ false不同对象new 出来System.out.println(c.intern()d.intern());// ✅ true都指向常量池3.4 String 常量池的作者选择设计点选择原因位置JDK 7 在堆避免 PermGen OOM大小-XX:StringTableSizeN可调去重字面量自动去重节省内存回收不被引用时回收避免泄露四、中间件源码剖析数据库连接池4.1HikariCP享元思想HikariCP 是最快的数据库连接池本质是享元思想/** * ✅ HikariCP连接池享元 */publicclassHikariPoolimplementsHikariPoolMXBean,AutoCloseable{/** * ✅ 享元池连接对象 */privatefinalConcurrentBagPoolEntryconnectionBag;/** * ✅ 工厂方法获取连接 */publicConnectiongetConnection()throwsSQLException{// 1️⃣ 从池里拿PoolEntrypoolEntryconnectionBag.borrow(getMaximumBorrowWaitTime(),TimeUnit.MILLISECONDS);if(poolEntrynull){// 2️⃣ 池空了创建新连接...}returnpoolEntry.connection;}/** * ✅ 归还连接 */publicvoidrecycle(PoolEntrypoolEntry){connectionBag.requite(poolEntry);}}关键点关键点解释connectionBag享元池borrow()借连接享元复用recycle()还连接池空新建连接4.2 享元 vs 连接池维度享元连接池生命周期长期常驻借还归还不归还用完归还创建永不创建池空时创建共享完全共享短期共享业务启示数据库连接池本质是对象池不是经典享元。但用了享元思想连接对象复用避免每次新建。4.3 线程池享元思想/** * ✅ ThreadPoolExecutor线程池 */publicclassThreadPoolExecutorextendsAbstractExecutorService{/** * Worker复用线程 */privatefinalclassWorkerextendsAbstractQueuedSynchronizerimplementsRunnable{finalThreadthread;RunnablefirstTask;Worker(RunnablefirstTask){this.firstTaskfirstTask;this.threadgetThreadFactory().newThread(this);}}/** * 核心池常驻线程 */privatefinalHashSetWorkerworkersnewHashSet();}关键点关键点解释Worker线程的享元对象workers线程池享元池复用线程反复执行任务五、为什么框架作者这么设计读源码看的是实现悟的是设计哲学。5.1 框架作者选享元的 4 个核心考虑序号考虑举例①对象重复创建Integer / String②内存敏感大数据量场景③对象可识别内部状态相同④共享安全不可变对象5.2 享元 vs 缓存源码里怎么选场景选哪个原因共享对象享元节省内存缓存数据缓存加速查询重用资源对象池借还机制缓存数据库缓存数据查询5.3 框架作者不选享元的 3 种情况序号场景原因①对象不重复享元没意义②对象可变共享会出问题③线程不安全用 ThreadLocal 替代5.4 享元在企业级应用的 3 种典型用法用法 1Integer 缓存 复用小整数对象 → 自动装箱性能提升 用法 2商品标签池 复用标签对象 → 100 万级共享 用法 3数据库连接池 复用数据库连接 → 创建/销毁开销节省六、借鉴到我们项目3 个抄作业实践6.1 实践 1字典数据享元场景大白商城的状态枚举订单状态/支付状态/物流状态共享。完整实现/** * 借鉴 Integer 缓存字典数据享元 */publicclassDictionaryCache{/** * ✅ 享元池状态码 → 状态描述 */privatestaticfinalConcurrentHashMapString,StringCACHEnewConcurrentHashMap();static{// 预加载CACHE.put(PENDING_PAY,待支付);CACHE.put(PAID,已支付);CACHE.put(SHIPPED,已发货);CACHE.put(COMPLETED,已完成);CACHE.put(CANCELLED,已取消);}publicstaticStringgetDescription(Stringcode){returnCACHE.computeIfAbsent(code,k-未知状态: k);}}// 使用StringdescDictionaryCache.getDescription(PAID);// 已支付6.2 实践 2商品标签享元池场景大白商城商品标签“热销”/“新品”/“包邮”共享。完整实现/** * 借鉴 Integer 缓存商品标签享元池 */ComponentpublicclassTagPool{privatestaticfinalConcurrentHashMapString,TagPOOLnewConcurrentHashMap();static{// 预热项目启动时预加载POOL.put(热销,newGoodsTag(热销,red,));POOL.put(新品,newGoodsTag(新品,green,✨));POOL.put(包邮,newGoodsTag(包邮,blue,));POOL.put(限时特惠,newGoodsTag(限时特惠,orange,⏰));}publicstaticTaggetTag(Stringname){returnPOOL.computeIfAbsent(name,k-newGoodsTag(k,gray,️));}publicstaticintpoolSize(){returnPOOL.size();}}// 使用TagtagTagPool.getTag(热销);// 共享6.3 实践 3枚举享元场景大白商城业务枚举共享避免重复创建。完整实现/** * 借鉴 Boolean 享元枚举值共享 */publicenumOrderStatus{PENDING_PAY(PENDING_PAY,待支付),PAID(PAID,已支付),SHIPPED(SHIPPED,已发货),COMPLETED(COMPLETED,已完成),CANCELLED(CANCELLED,已取消);privatefinalStringcode;privatefinalStringdescription;OrderStatus(Stringcode,Stringdescription){this.codecode;this.descriptiondescription;}/** * ✅ 享元枚举值全局唯一 */publicstaticOrderStatusof(Stringcode){for(OrderStatusstatus:values()){if(status.code.equals(code)){returnstatus;// 共享同一对象}}thrownewIllegalArgumentException(未知状态: code);}}// 使用OrderStatusstatusOrderStatus.of(PAID);System.out.println(statusOrderStatus.PAID);// ✅ true同一对象收益优化点效果字典数据共享节省 99% 内存标签池100 万级共享枚举享元全局唯一七、本篇小结 下一模式预告7.1 本篇小结5 个核心要点JDK Integer 缓存经典享元-128 ~ 127共享。JDK String 常量池字面量享元intern()加入常量池。中间件连接池 / 线程池享元思想借还机制。设计哲学缓存池 复用 不可变。借鉴价值字典数据 / 标签池 / 枚举享元任何大量重复对象都用享元。7.2 一句话总结享元在源码里不是省内存的形象化而是缓存池 复用的工业级架构。Integer 缓存小整数String 常量池字面量连接池复用连接。学享元先学 Integer 缓存。7.3 知识脑图享元源码剖析 ├── JDK 包装类 │ ├── Integer-128 ~ 127 │ ├── Long-128 ~ 127 │ ├── Short / Byte全范围 │ ├── Character0 ~ 127 │ └── Boolean2 个值 ├── JDK String │ └── StringTable常量池 ├── 中间件 │ ├── HikariCP连接池 │ ├── Druid连接池 │ └── ThreadPoolExecutor线程池 └── 借鉴价值 ├── 字典数据享元 ├── 标签池享元 └── 枚举享元7.4 下一模式预告第 26 篇【策略模式 - 业务实战篇】大白商城支付策略的算法切换下一篇我们会实战策略模式回答 4 个问题多支付渠道支付宝/微信/银联怎么灵活切换策略 vs 工厂方法 / 状态模式怎么区分Spring 的ResourceLoader怎么借鉴策略策略 工厂方法怎么组合并附完整的支付策略实战代码含多支付策略 上下文 单元测试可直接复制到 IDEA 跑。觉得对您有帮助麻烦点点关注啦您的关注是我创作的最大动力~
返回列表