享元模式
把现有的资源重复利用起来
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 吧