享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
共享对象,复用对象实例。
主要角色:
类图:
抽象享元类,定义所有享元的公共方法。
public interface IFlyweight {
void sayHello();
}
具体享元类,实现方法:
public class ConcreteFlyweight implements IFlyweight {
private String hello;
public ConcreteFlyweight(String hello) {
this.hello = hello;
}
public void sayHello() {
System.out.println(hello);
}
}
享元工厂,使用 Map 缓存享元,在创建享元的接口里,先走缓存获取,没有的话再创建。
public class FlyweightFactory {
private Map flyweightMap = new HashMap();
public IFlyweight getFlyweight(String key) {
IFlyweight flyweight = flyweightMap.get(key);
if (flyweight == null) {
flyweight = new ConcreteFlyweight(key);
flyweightMap.put(key, flyweight);
}
return flyweight;
}
}
使用地方:
public class TestFlyweight {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
// 新对象
IFlyweight flyweight = factory.getFlyweight("sayHello");
flyweight.sayHello();
// 新对象
IFlyweight flyweight1 = factory.getFlyweight("你好");
flyweight1.sayHello();
// 对象复用
IFlyweight flyweight2 = factory.getFlyweight("sayHello");
flyweight2.sayHello();
System.out.println("复用:" + (flyweight == flyweight2));
// 不可复用对象
IFlyweight flyweight3 = new UnshareConcreteFlyweight();
flyweight3.sayHello();
}
}
可以看到,后续一样的 “sayHello” 对象没有重复创建,而是复用了之前创建的。
享元类设计的关键步骤就是把内部状态和外部状态进行剥离。
应用在这些场景:
DruidDataSource 属于数据库连接池,用来缓存数据库连接。
数据库连接可复用,无需大量创建。
缓存池使用了数组实现,连接和连接相关的配置信息被封装在 DruidConnectionHolder 中了。
private volatile DruidConnectionHolder[] connections;
具体的获取逻辑在 getConnectionInternal 中实现。先从缓存读取,没有的话阻塞等待新创建的连接然后返回。
Java 使用 Integer 包装类的时候,使用工厂方法 valueOf 的地方有应用了享元模式。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以内部使用了 IntegerCache 来进行缓存。
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() {}
}
可以看到,在 [-128,127] 的包装类已经在类加载过程中,提前放入了缓存中。
这个区间的包装类使用频繁,这里可以进行复用。