享元模式

享元设计模式UML类图

适配器设计模式UML类图

享元设计模式java实现

/**
* Interface for Potions.
药水
*/
public interface Potion {

void drink();
}

/**
* Enumeration for potion types.
*/
public enum PotionType {

HEALING, INVISIBILITY, STRENGTH, HOLY_WATER, POISON
}

/**
* HealingPotion.
治疗药水
*/
public class HealingPotion implements Potion {

private static final Logger LOGGER = LoggerFactory.getLogger(HealingPotion.class);

@Override
public void drink() {
LOGGER.info("You feel healed. (Potion={})", System.identityHashCode(this));
}
}

/**
* HolyWaterPotion.
圣水药水
*/
public class HolyWaterPotion implements Potion {

private static final Logger LOGGER = LoggerFactory.getLogger(HolyWaterPotion.class);

@Override
public void drink() {
LOGGER.info("You feel blessed. (Potion={})", System.identityHashCode(this));
}
}

/**
* InvisibilityPotion.
隐形药水
*/
public class InvisibilityPotion implements Potion {

private static final Logger LOGGER = LoggerFactory.getLogger(InvisibilityPotion.class);

@Override
public void drink() {
LOGGER.info("You become invisible. (Potion={})", System.identityHashCode(this));
}
}

/**
* PoisonPotion.
毒药
*/
public class PoisonPotion implements Potion {

private static final Logger LOGGER = LoggerFactory.getLogger(PoisonPotion.class);

@Override
public void drink() {
LOGGER.info("Urgh! This is poisonous. (Potion={})", System.identityHashCode(this));
}
}

/**
* StrengthPotion.
力量药水
*/
public class StrengthPotion implements Potion {

private static final Logger LOGGER = LoggerFactory.getLogger(StrengthPotion.class);

@Override
public void drink() {
LOGGER.info("You feel strong. (Potion={})", System.identityHashCode(this));
}
}

/**
* PotionFactory is the Flyweight in this example. It minimizes memory use by sharing object
* instances. It holds a map of potion instances and new potions are created only when none of the
* type already exists.
在此示例中,PotionFactory是Flyweight。 通过共享对象实例,可最大程度地减少内存使用。 它拥有药水实例的映射,并且仅在不存在任何药水类型时才创建新药水。
*/
public class PotionFactory {

private final Map<PotionType, Potion> potions;

public PotionFactory() {
potions = new EnumMap<>(PotionType.class);
}

Potion createPotion(PotionType type) {
var potion = potions.get(type);
if (potion == null) {
switch (type) {
case HEALING:
potion = new HealingPotion();
potions.put(type, potion);
break;
case HOLY_WATER:
potion = new HolyWaterPotion();
potions.put(type, potion);
break;
case INVISIBILITY:
potion = new InvisibilityPotion();
potions.put(type, potion);
break;
case POISON:
potion = new PoisonPotion();
potions.put(type, potion);
break;
case STRENGTH:
potion = new StrengthPotion();
potions.put(type, potion);
break;
default:
break;
}
}
return potion;
}
}

/**
* AlchemistShop holds potions on its shelves. It uses PotionFactory to provide the potions.
AlchemistShop在架子上放着药水。 它使用PotionFactory提供药水。
*/
public class AlchemistShop {

private static final Logger LOGGER = LoggerFactory.getLogger(AlchemistShop.class);

private List<Potion> topShelf;
private List<Potion> bottomShelf;

/**
* Constructor.
*/
public AlchemistShop() {
var factory = new PotionFactory();
topShelf = List.of(
factory.createPotion(PotionType.INVISIBILITY),
factory.createPotion(PotionType.INVISIBILITY),
factory.createPotion(PotionType.STRENGTH),
factory.createPotion(PotionType.HEALING),
factory.createPotion(PotionType.INVISIBILITY),
factory.createPotion(PotionType.STRENGTH),
factory.createPotion(PotionType.HEALING),
factory.createPotion(PotionType.HEALING)
);
bottomShelf = List.of(
factory.createPotion(PotionType.POISON),
factory.createPotion(PotionType.POISON),
factory.createPotion(PotionType.POISON),
factory.createPotion(PotionType.HOLY_WATER),
factory.createPotion(PotionType.HOLY_WATER)
);
}

/**
* Get a read-only list of all the items on the top shelf.
*
* @return The top shelf potions
*/
public final List<Potion> getTopShelf() {
return List.copyOf(this.topShelf);
}

/**
* Get a read-only list of all the items on the bottom shelf.
*
* @return The bottom shelf potions
*/
public final List<Potion> getBottomShelf() {
return List.copyOf(this.bottomShelf);
}

/**
* Enumerate potions.
*/
public void enumerate() {
LOGGER.info("Enumerating top shelf potions\n");
topShelf.forEach(Potion::drink);
LOGGER.info("Enumerating bottom shelf potions\n");
bottomShelf.forEach(Potion::drink);
}
}

/**
* Flyweight pattern is useful when the program needs a huge amount of objects. It provides means to
* decrease resource usage by sharing object instances.
*
* <p>In this example {@link AlchemistShop} has great amount of potions on its shelves. To fill the
* shelves {@link AlchemistShop} uses {@link PotionFactory} (which represents the Flyweight in this
* example). Internally {@link PotionFactory} holds a map of the potions and lazily creates new ones
* when requested.
*
* <p>To enable safe sharing, between clients and threads, Flyweight objects must be immutable.
* Flyweight objects are by definition value objects.
当程序需要大量对象时,Flyweight模式非常有用。 它提供了通过共享对象实例来减少资源使用的方法。
<p>在此示例中,{@ link AlchemistShop}的货架上有大量药水。 为了填充货架,{@ link AlchemistShop}使用了{@link PotionFactory}(在此代表着Flyweight
例)。 内部{@link PotionFactory}拥有一张药水地图,并在需要时懒惰地创建新药水。
<p>要在客户端和线程之间实现安全共享,Flyweight对象必须是不可变的。 Flyweight对象是定义值对象。
*/
public class App {

/**
* Program entry point.
*
* @param args command line args
*/
public static void main(String[] args) {
var alchemistShop = new AlchemistShop();
alchemistShop.enumerate();
}
}

应用场景

String中的享元模式

Java中将String类定义为final(不可改变的),JVM中字符串一般保存在字符串常量池中,java会确保一个字符串在常量池中只有一个拷贝,这个字符串常量池在JDK6.0以前是位于常量池中,位于永久代,而在JDK7.0中,JVM将其从永久代拿出来放置于堆中。
我们做一个测试:

public class Main {
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;
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
}
}

String类的final修饰的,以字面量的形式创建String变量时,jvm会在编译期间就把该字面量hello放到字符串常量池中,由Java程序启动的时候就已经加载到内存中了。这个字符串常量池的特点就是有且只有一份相同的字面量,如果有其它相同的字面量,jvm则返回这个字面量的引用,如果没有相同的字面量,则在字符串常量池创建这个字面量并返回它的引用。

由于s2指向的字面量hello在常量池中已经存在了(s1先于s2),于是jvm就返回这个字面量绑定的引用,所以s1==s2。

s3中字面量的拼接其实就是hello,jvm在编译期间就已经对它进行优化,所以s1和s3也是相等的。

s4中的new String(“lo”)生成了两个对象,lo,new String(“lo”),lo存在字符串常量池,new String(“lo”)存在堆中,String s4 = “hel” + new String(“lo”)实质上是两个对象的相加,编译器不会进行优化,相加的结果存在堆中,而s1存在字符串常量池中,当然不相等。s1==s9的原理一样。

s4==s5两个相加的结果都在堆中,不用说,肯定不相等。

s1==s6中,s5.intern()方法能使一个位于堆中的字符串在运行期间动态地加入到字符串常量池中(字符串常量池的内容是程序启动的时候就已经加载好了),如果字符串常量池中有该对象对应的字面量,则返回该字面量在字符串常量池中的引用,否则,创建复制一份该字面量到字符串常量池并返回它的引用。因此s1==s6输出true。

Integer 中的享元模式

使用例子如下:

public static void main(String[] args) {
Integer i1 = 12 ;
Integer i2 = 12 ;
System.out.println(i1 == i2);

Integer b1 = 128 ;
Integer b2 = 128 ;
System.out.println(b1 == b2);
}

输出是

true
false

为什么第一个是true,第二个是false?
反编译后可以发现 Integer b1 = 128; 实际变成了 Integer b1 = Integer.valueOf(128);,所以我们来看 Integer 中的 valueOf 方法的实现

public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int var0) {
return var0 >= -128 && var0 <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
}
//...省略...
}

IntegerCache 缓存类

//是Integer内部的私有静态类,里面的cache[]就是jdk事先缓存的Integer。
private static class IntegerCache {
static final int low = -128;//区间的最低值
static final int high;//区间的最高值,后面默认赋值为127,也可以用户手动设置虚拟机参数
static final Integer cache[]; //缓存数组

static {
// high value may be configured by property
int h = 127;
//这里可以在运行时设置虚拟机参数来确定h :-Djava.lang.Integer.IntegerCache.high=250
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {//用户设置了
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);//虽然设置了但是还是不能小于127
// 也不能超过最大值
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
//循环将区间的数赋值给cache[]数组
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}

private IntegerCache() {}
}

可以看到 Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象

Long中的享元模式

public final class Long extends Number implements Comparable<Long> {
public static Long valueOf(long var0) {
return var0 >= -128L && var0 <= 127L ? Long.LongCache.cache[(int)var0 + 128] : new Long(var0);
}
private static class LongCache {
private LongCache(){}

static final Long cache[] = new Long[-(-128) + 127 + 1];

static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
//...
}

同理,Long 中也有缓存,不过不能指定缓存最大值

Apache Commons Pool2中的享元模式

对象池化的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。用于充当保存对象的“容器”的对象,被称为“对象池”(Object Pool,或简称Pool)

Apache Commons Pool实现了对象池的功能。定义了对象的生成、销毁、激活、钝化等操作及其状态转换,并提供几个默认的对象池实现。

有几个重要的对象:

PooledObject(池对象):用于封装对象(如:线程、数据库连接、TCP连接),将其包裹成可被池管理的对象。
PooledObjectFactory(池对象工厂):定义了操作PooledObject实例生命周期的一些方法,PooledObjectFactory必须实现线程安全。
Object Pool (对象池):Object Pool负责管理PooledObject,如:借出对象,返回对象,校验对象,有多少激活对象,有多少空闲对象。

// 对象池
private final Map<S, PooledObject<S>> allObjects = new ConcurrentHashMap<S, PooledObject<S>>();

重要方法:
borrowObject:从池中借出一个对象。
returnObject:将一个对象返还给池。