享元模式(Flyweight Pattern)又称为轻量级模式,是对象池的一种实现。很类似线程池,线程池可以避免不停的创建和销毁对象,消耗性能。该模式利用共享的方式来支持大量细粒度的对象,将多个对同一对象集中起来,不必每个访问者创建一个单独的对象,从而减低内存的消耗。
享元模式把一个对象的状态分为内部状态和外部状态,内部状态即是不变的,外部状态是变化的,然后通过共享不变的部分,达到减少对象数量并节约内存的目的。
享元模式的本质就是缓存共享对象,降低内存消耗。
享元模式的标准类图如下:
从类图中可以看出,享元模式有三个角色:
抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
具体享元角色(Concrete Flyweight):实现抽象享元角色中所规定的接口。
享元工厂角色(Flyweight Factory):负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
创建Flyweight类:
public interface Flyweight { void operation();}
创建Concrete Flyweight类:
public class ConcreteFlyweight implements Flyweight {
private String state;
public ConcreteFlyweight(String state) {
this.state = state;
}
@Override
public void operation() {
System.out.println("HashCode:"+System.identityHashCode(this));
System.out.println("state:"+ this.state);
}
}
创建FlyweightFactory类:
public class FlyweightFactory {
private Map<String,Flyweight> flyweights= new HashMap<>();
Flyweight getFlyweight(String state){
if(!flyweights.containsKey(state)){
Flyweight flyweight = new ConcreteFlyweight(state);
flyweights.put(state,flyweight);
}
return flyweights.get(state);
}
}
客户端Client代码:
public class Client { public static void main(String[] args) { FlyweightFactory flyweightFactory = new FlyweightFactory(); Flyweight flyweight1 = flyweightFactory.getFlyweight("state1") ; Flyweight flyweight2 = flyweightFactory.getFlyweight("state1") ; Flyweight flyweight3 = flyweightFactory.getFlyweight("state3") ; flyweight1.operation(); flyweight2.operation(); flyweight3.operation(); }}
运行结果:
HashCode:1867083167state:state1HashCode:1867083167state:state1HashCode:1915910607state:state3
从上面的结果可以看出,flyweight1和flyweight2公用了一个对象,而不是创建了两个对象,而flyweight3对象再缓存中不存在,所以就创建了对象。
享元模式应用的场景
当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,一个对象就可以提供给多处需要使用的地方,避免大量同一对象的多次创建,消耗大量的内存空间。享元模式其实就是工厂模式的改进,享元模式同样要求创建一个或者一组对象,并且就是通过工厂方法生成对象,只不过享元模式中的工厂方法增加了缓存这一功能。主要应用场景如下:
1、常常应用于系统底层的开发,以便解决系统的性能问题。
2、系统有大量相似的对象,需要缓冲池的场景。
享元模式源码中应用
String中的享元模式
Java中将String定义为final,JVM中字符串一般保存再字符串常量池中,Java会确保一个字符串在常量池中只有一个拷贝,这个字符串常量池在JDK1.6以前是位于常量池中,位于永久代,而在JDK1.7中,JVM将其从永久代拿出来放到了堆中。
public static void main(String[] args) { String s1 = "hello"; String s2 = "hello"; String s3 = "he" + "llo"; String s4 = "hel" + new String("lo"); String s5 = new String("hello"); String s6 = s5.intern(); String s7 = "h"; String s8 = "ello"; String s9 = s7 + s8; //由于s2指向的字面量“hello”在常量池中已经存在了(s1先于s2),于是JVM就返回了这个字面量绑定的引用,所以s1==s2为true System.out.println(s1==s2);//true //s3中字面量的拼接其实就是hello,JVM在编译期间就已经进行了优化,所以s1==s3为true System.out.println(s1==s3);//true //s4中new String(“lo”)生成了两个对象,lo,new String(“lo”)存在字符串常量池,new String(“lo”)存在堆中, tring s4 = "hel" + new String("lo")实质上是两个对象相加,编译器不会进行优化,相加的结果存在堆中,而s1在常量池中,所以s1==s4为false,s1==s9原理也一样 System.out.println(s1==s4);//false System.out.println(s1==s9);//false //s4和s5都是两个相加的结果在堆中,肯定是不相等的 System.out.println(s4==s5);//false //s5.intern()方法能使一个位于堆中的字符串在运行期间动态加入到字符串常量池中(字符串常量池的内容是程序启动的时候就已经加载好了),如果字符串常量池中有该对象对相应的字面量,则返回该字面量在字符串常量池的应用,否则,创建复制一份该字面量到字符串常量池并返回她的应用。因此s1==s6为true System.out.println(s1==s6);//true }
Integer中的享元模式
static final int low = -128;static final int high;//看源码high后面复制为127,也就是-128到127之间,是被缓存的,面试题中也常被问到这个问题。public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
例子有很多,这里就到这吧
享元模式的优缺点
优点:
1、减少对象的创建,降低内存中对象的数量,降低系统的内存,提高效率;
2、减少内存之外的其他资源占用;
缺点:
1、关注内、外部状态,关注线程安全问题;
2、使系统、程序的逻辑复杂化;