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