设计模式-享元模式
把现有的资源重复利用起来
Java中常见的OOm有以下两种
内存泄漏
无意识的代码缺陷,导致内存泄漏,JVM不能获得连续的内存空 间。
对象太多
代码写得很烂,产生的对象太多,内存被耗尽。现没有内存泄漏,那只有一种原因
代码太差把内存耗尽。
有的对象我们用完可以复用的,不用等到oom
定义
又称为轻量级模式,对象池的一种实现
类似于线程池,线程池可以避免不停地创建和销毁多个对象,消耗性能
提供了减少对象数量而改善应用所需的对象结构的方式
共享细粒度对象,将多个对同一对象的访问集中起来
结构型模式
生活中的例子
中介
房源共享的机制
全国社保联网
公共类图

抽象的享元角色Iflyweight
Iflyweight
它简单地说就是一个产品的抽象类,同时定义出对象的外部状态和 内部状态的接口或实现。
具体的享元角色ConcreteFlyweight
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如果用线程池,变量不记得回收可能出现问题
连接池的维护
细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。
Last updated