一、什么是享元模式? 享元模式(Flyweight pattern)又叫轻量级模式,是对象池的一种标签。类似线程池,线程池可以避免不停的创建和销毁对象,消耗性能。享元模式可以减少对象数量,其宗旨是共享细粒度对象,将多个对同一对象的访问集中起来,属于结构型设计模式
二、享元模式的写法极其uml图 1.享元模式的uml图:
主要的3个角色 1.抽象享元角色(IFlyweight): 享元对象抽象基类或接口,同时定义出对象的外部状态和内部状态的接口或实现。 2.具体享元角色(ConcreteFlyweight):实现抽象角色定义的业务。该角色的内部状态处理应该与环境无关,不会出现一个操作改变内部状态,同时修改了外部状态的情况 3.享元工厂(FlyweightFactory):负责管理享元对象池和创建享元对象 tip:什么是外部状态和内部状态,我们后面根据例子来详情了解
2.通用写法
IFlyweight:
/** * 抽象享元角色 */ public interface IFlyweight {
void operation(String extrinsicState);
}
ConcreteFlyweight:
/** * 具体享元角色 */ public class ConcreteFlyweight implements IFlyweight {
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) { this.intrinsicState = intrinsicState; }
@Override public void operation(String extrinsicState) { System.out.println("object address: " + System.identityHashCode(this)); System.out.println("extrinsicState: " + extrinsicState); System.out.println("intrinsicState: " + intrinsicState); } }
FlyweightFactory:
/** * 享元工厂 */ public class FlyweightFactory {
public static Map<String, IFlyweight> pool = new ConcurrentHashMap<>();
public static IFlyweight getFlyweight(String intrinsicState) { if (!pool.containsKey(intrinsicState)) { IFlyweight flyweight = new ConcreteFlyweight(intrinsicState); pool.put(intrinsicState, flyweight); } return pool.get(intrinsicState); } }
结果:
public class Client {
public static void main(String[] args) { IFlyweight flyweight1 = FlyweightFactory.getFlyweight("aa"); IFlyweight flyweight2 = FlyweightFactory.getFlyweight("aa"); IFlyweight flyweight3 = FlyweightFactory.getFlyweight("cc"); flyweight1.operation("wwwww"); flyweight2.operation("ttttt"); flyweight3.operation("wwwww"); } }
这样我们可以重复使用一些操作频繁的对象,避免重复创建
1.string 中的享元模式
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; 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? 一看到,想当然就是等于,因为两个字符串的值是一样的嘛,但是,我们都知道到String是引用类型,s1==s2其实是地址比较,并不是值的比较。 当以字面量的形式创建String变量的时候,JVM会在编译期间就把该字面量"hello"放到字符串常量池(享元模式)中,这个字符串常量池的特点就是有且只有一份相同的字面量。如果有其他相同的字面量,JVM则返回这个字面量的引用,如果没有相同的字面量,则在字符串常量池中创建这个字面量并返回它的引用。
为什么s1等于s3? s3中的字面量的拼接其实就是"hello",JVM在编译期间就已经对它进行了优化,所有s1等于s3
为什么s1不等于s4? s4中new String(“lo”)生成了2个对象,这也是一道经典的面试题。"lo"存在与常量池当中,new String(“lo”)在堆当中,String s4 = “hel” + new String(“lo”);两个对象的相加存在于堆当中,而s1在常量池当中,所以不相等。 为什么s1不等于s4? 同理
为什么s4不等于s5? 都是在堆当中,一定不相等。
为什么s1等于s6? s5.intern()方法能使一个位于堆中的字符串在运行期间动态地加入到字符串常量池当中,如果常量池中有,则返回引用。没有加入常量池,返回引用。
2.Integer 中的享元模式 看下面的例子,也是一道经典面试题
Integer a = Integer.valueOf(100); Integer b = 100;
Integer c = Integer.valueOf(1000); Integer d = 1000;
System.out.println(a==b); System.out.println(c==d); 运行结果:
为什么呢? 之所以会有这个结果,因为用到享元模式,我们来看下Integer类中的源码。
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } 我们发现在IntegerCache.low和 IntegerCache.high之间的值都会取缓存。
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() {} }
我们从源码中发现这个low=-128,high=127,然后循环将这个区间的值,放入到了缓存当中。
什么内部状态? 这些具体的区间值,127,-128,这些值是不会根据外部的使用改变的,这种就是内部状态。 什么是外部状态? 我们可以想象在线程池当中,当我们拿去一个连接的时候,这个连接就是一个“被使用”的状态,它是受外部环境改变的一个状态。这种就是外部状态。
四、优点和缺点 优点: 1.减少对象的创建,降低内存中对象的数量,降低系统内存,提高系性能。 缺点: 1.需要关注内、外部状态,关注线程安全问题(同时访问的相同的缓存) 2.系统程序负责化了(比如上面的Integer问题,不去了解,头上100个问号)