享元模式
把现有的资源重复利用起来
Java中常见的OOm有以下两种
内存透露
- 有意识的代码缺点,导致内存透露,JVM不能取得间断的内存空 间。
对象太多
代码写得很烂,产生的对象太多,内存被耗尽。现没有内存透露,那只有一种起因
- 代码太差把内存耗尽。
- 有的对象咱们用完能够复用的,不必等到oom
定义
- 又称为轻量级模式,对象池的一种实现
- 相似于线程池,线程池能够防止不停地创立和销毁多个对象,耗费性能
- 提供了缩小对象数量而改善利用所需的对象构造的形式
共享细粒度对象,将多个对同一对象的拜访集中起来结构型模式
生存中的例子
中介
- 房源共享的机制
- 全国社保联网
公共类图
形象的享元角色Iflyweight
它简略地说就是一个产品的抽象类,同时定义出对象的内部状态和 外部状态的接口或实现。
具体的享元角色ConcreteFlyweight
具体的一个产品类,实现形象角色定义的业务。该角色中须要留神 的是外部状态解决应该与环境无关,不应该呈现一个操作扭转了外部状 态,同时批改了内部状态,这是相对不容许的。
public class ConcreteFlyweight implements IFlyweight { private String intrinsicState; public ConcreteFlyweight(String intrinsicState) { this.intrinsicState = intrinsicState; } public void operation(String extrinsicState) { System.out.println("Object address: " + System.identityHashCode(this)); System.out.println("IntrinsicState: " + this.intrinsicState); System.out.println("ExtrinsicState: " + extrinsicState); }}
FlyweightFactory享元工厂
- 职责非常简单,就是结构一个池容器,同时提供从池中取得对象的办法。
- 享元模式的目标在于使用共享技术,使得一些细粒度的对象能够共 享,咱们的设计的确也应该这样,多应用细粒度的对象,便于重用或重构。
public class FlyweightFactory { private static Map<String, IFlyweight> pool = new HashMap<String, IFlyweight>(); // 因为外部状态具备不变性,因而作为缓存的键 public static IFlyweight getFlyweight(String intrinsicState) { if (!pool.containsKey(intrinsicState)) { IFlyweight flyweight = new ConcreteFlyweight(intrinsicState); pool.put(intrinsicState, flyweight); } return pool.get(intrinsicState); }}
状态
外部状态
- 外部状态是对象可共享进去的信息,存储在享元对象外部
- 不会随环境扭转而扭转,它们能够作为 一个对象的动静附加信息,不用间接贮存在具体某个对象中,属于能够共享的局部。
内部状态
对象能够被依赖的标记,不可共享的状态
Connection
- 随外部环境变动而变动的属性
- 例如连贯的沉闷状态,是否可用会随着变动而变动
利用场景
经常用于零碎底层开发,用来解决零碎的性能问题
零碎有大量类似对象,须要换冲池的场景
例子
连接池
在源码中的应用
String
会在内存中缓存了字符串
new String("lo")
创立了两个对象Lo
先创立在了内存池,newString 创立了一个新的对象
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 }
s1和s2存储在同一个内存地址
s1==s3 字面量jdk间接帮忙咱们放到一起做了优化解决
Integer
-128-127
public static void main(String[] args) { Integer a = Integer.valueOf(100); Integer b = 100; Integer c = Integer.valueOf(1000); Integer d = 1000; /** * 想查看Integer缓存的大小,debug一下看一下cache的值<br> */ System.out.println("a==b:" + (a==b)); System.out.println("c==d:" + (c==d));}
能够看到在Integer外部保护了一个缓存,JVM认为-128-127他们的应用频率十分高,所以做了一个对象缓存。
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() {}}public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i);}
Long
Long同理
public static Long valueOf(long l) { final int offset = 128; if (l >= -128 && l <= 127) { // will cache return LongCache.cache[(int)l + offset]; } return new Long(l);}
-128-127
总结
优缺点
长处
- 缩小对象的创立,升高内存中对象的数量,升高零碎的内存,提高效率
- 缩小内存之外的其余资源的占用
毛病
- 须要关注内外部状态
线程平安问题
暗藏了很多信息
- 例如Integer
- 让零碎的程序和逻辑变得复杂
享元和代理的区别
- 代理是关注与性能的加强
- 享元不是为了性能减少而是为了防止反复创建对象
享元和单例的关系
- 个别享元会配合单例一起应用
留神
- 肯定须要留神线程平安问题,容器记得应用线程平安的容器
问题
什么时候应用享元模式呢
零碎中存在大量的类似对象
- 例如String,Integer,Long外部保护的缓存
须要缓冲池的场景
例如说线程池的保护
- 须要留神ThreadLocal如果用线程池,变量不记得回收可能呈现问题
- 连接池的保护
- 细粒度的对象都具备较靠近的内部状态,而且外部状态与环境无关,也就是说对象没有特定身份。
我的笔记仓库地址gitee 快来给我点个Star吧