享元模式

# 享元模式(Flyweight Pattern) ## 前言 在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。 ## 定义 享元模式,又称为轻量级模式,运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、 避免大量相似类的开销,从而提高系统资源的利用率。它是对象池的一种实现,类似于线程池,线程池可以避免不停的创建个销毁多个对象,消耗性能(用户态和内核态); **【宗旨】**:共享细粒度对象,将多个对同一对象的访问集中起来;属于结构性模式; ## 内部状态和外部状态 在共享对象的过程中,又两种状态: **内部状态:** 内部状态指对象共享出来的信息,存储在享元信息内部,并且不回随环境的改变而改变; > 就是对象所共有的信息,比如火车票,多有从北京-上海的对象可以共用一个,而不是有1000张票,创建1000个对象; **外部状态:** 外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享; > 也是不叫好理解的,当火车票被你购买之后,就和你的身份证号(唯一)绑定了,这属于外部状态; ## 使用场景 1、常常应用于系统底层的开发,以便解决系统的性能问题; 2、系统有大量相似对象,需要缓冲池的地方; ## 举个例子🌰 ### 享元实体类 ```text public class Examination { private String name; private String phone; private String subject; public Examination(String subject) { super(); this.subject = subject; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String toString() { return "Examination [name=" + name + ", phone=" + phone + ", subject=" + subject + "]"; } } ``` ### 享元工厂 ```text public class ExaminationFactory { // 对象池 private static Map<String, Examination> pool = new HashMap<>(); public static Examination getexExamination(String name, String phone, String subject) { // 通过学科判断是否存在这个对象 if (pool.containsKey(subject)) { Examination examination = pool.get(subject); examination.setName(name); examination.setPhone(phone); System.out.println("在缓存池取用对象:" + examination.toString()); return examination; } else { Examination examination = new Examination(subject); pool.put(subject, examination); examination.setName(name); examination.setPhone(phone); System.out.println("新建对象:" + examination.toString()); return examination; } } } ``` ### 测试类 ```text public class Client { public static void main(String[] args) { Examination A = ExaminationFactory.getexExamination("A", "13812345678", "软件工程"); Examination B = ExaminationFactory.getexExamination("B", "13812341234", "软件工程"); Examination C = ExaminationFactory.getexExamination("C", "13812341328", "电子信息工程"); Examination D = ExaminationFactory.getexExamination("D", "13812345111", "桥梁工程工程"); Examination E = ExaminationFactory.getexExamination("E", "13812345444", "软件工程"); System.err.println(A.hashCode()); System.err.println(B.hashCode()); } } ``` ### 测试结果 ```text 新建对象:Examination [name=A, phone=13812345678, subject=软件工程] 在缓存池取用对象:Examination [name=B, phone=13812341234, subject=软件工程] 新建对象:Examination [name=C, phone=13812341328, subject=电子信息工程] 新建对象:Examination [name=D, phone=13812345111, subject=桥梁工程工程] 在缓存池取用对象:Examination [name=E, phone=13812345444, subject=软件工程] 1528902577 1528902577 ``` ## 实际应用 1、JDK -- String ```text public static void main(String[] args) { String s1 = "hello";// "hello"是编译器常量, String s1是运行时,把常量地址赋值给他<br> String s2 = "hello"; String s3 = "he" + "llo"; // "he" + "llo" 两个常量相加,会在编译期处理<br> String s4 = "hel" + new String("lo"); // "hel" "lo" new String 共建了3个空间,然后拼接起来是一个新的空间(why?) String s5 = new String("hello"); // s5存放的是堆中中间 String s6 = s5.intern(); //拿到常量中的地址, String s7 = "h"; String s8 = "ello"; String s9 = s7 + s8; //为什么这个不一样,因为是变量相加所以编译期没有做优化 System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println("s1 " + System.identityHashCode(s1)); System.out.println("s2 " + System.identityHashCode(s2)); System.out.println("s3 " + System.identityHashCode(s3)); System.out.println("s4 " + System.identityHashCode(s4)); System.out.println("s5 " + System.identityHashCode(s5)); System.out.println("s6 " + System.identityHashCode(s6)); //s6为s5.intern()拿到的是常量池里的“hello” System.out.println("s7 " + System.identityHashCode(s7)); System.out.println("s8 " + System.identityHashCode(s8)); System.out.println("s9 " + System.identityHashCode(s9)); System.out.print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); System.out.println(s1==s2);//true System.out.println(s1==s3);//true System.out.println(s1==s4);//false System.out.println(s1==s9);//false System.out.println(s4==s5);//false System.out.println(s1==s6);//true } ``` ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7689e9454c9140819c515317ba2f3da8~tplv-k3u1fbpfcp-zoom-1.image) 2、JDK -- Integer ```text private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} } ``` 3、线程池 4、Tomcat连接池 ## 享元模式的优点✅ 1、减少对象的创建,降低内存中对象的数量,降低系统的内存使用,提升效率; 2、减少内存之外的其他资源占用,IO,带宽等; ## 享元模式的缺点 1、关注内部,外部状态,关注程序线程安全问题; 2、使系统、程序的逻辑变得复杂; ## 参考资料 1、[[设计模式] - 享元模式](https://juejin.cn/post/6924611630391115783) 2、[设计模式-享元模式](https://juejin.cn/post/6914489741706526734) 3、[设计模式-享元设计模式](https://juejin.cn/post/6844903841188577288)