享元设计模式
享元设计模式(Flyweight Pattern)是23种设计模式中广泛引用的其中一种,主要用在构建缓存对象的时候用到,不管是在Java,还是在Android中,都不可或缺,比如我们常见String字符串以及自定义的View中的TypeArray,线程池或者Message等等都有用到享元模式,共同的特点都是复用了已经存在的细粒度对象,并且在对象不需要的时候回收,方便再次使用,减少系统的消耗。
享元模式图
●FlyWeight 享元接口或者(抽象享元类),定义共享接口
●ConcreteFlyWeight 具体享元类,该类实例将实现共享
●FlyWeightFactory 享元工厂类,控制实例的创建和共享
●ConcreteConpositeFlyweight 它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。
享元模式内部状态VS外部状态
●内部状态是存储在享元对象内部,一般在构造函数的时候确定了,并且不会随环境改变而改变的状态,因此内部状态可以共享。
●外部状态是随环境改变而改变,外部状态在需要使用时通过客户端传入享元对象,由于是随环境而改变,因此内部无法存储,必须由客户端保存。
举个例子,从上海到北京的高铁来说,这里的内部状态是出发点上海和终点北京组成,这2个是无法改变的,而由于座位分等级,比如一等座,二等座,三等座等,这些价格也不一样,因此无法共享,只能有外部传入,也就是外部自己存储这些不共享的信息。
享元简单实例分析
下面看一个简单的例子:
public interface Travel {
void showDetailInfo(int level,int price);
}
复制代码
以从上海到北京的高铁为例子,
public class CrhTravel implements Travel {
//这里的出发点和终点是内部状态,是固定的,不会改变,可以共享
public String from;
public String to;
//等级和价格是外部状态,岁客户端动态变化的,不可共享,接口的参数里面也说明了这一点
public int level;
public int price;
public CrhTravel(String from, String to) {
this.from = from;
this.to = to;
}
@Override
public void showDetailInfo(int level, int price) {
this.level = level;
this.price = price;
System.out.println("购买从:" + from + " 到" + to + "的" + level + " 等高铁票+" + " ,价格为:" + price);
}
}
复制代码
下面再写一个缓存类,用来构建缓存池,因为在高峰期的时候,不可能每次拿票的时候都重新创建一个对象,这里的缓存池用并发的ConcurrentHashMap实现,以便在多线程状态下能正常工作
public class TravelFactory {
private static Map<String, Travel> sTravelMap = new ConcurrentHashMap<>();
public static Travel getTicket(String from, String to) {
String key = from + "-" + to;
if (sTravelMap.containsKey(key)) {
System.out.println("使用缓存==>" + key);
return sTravelMap.get(key);
} else {
System.out.println("创建对象==>" + key);
Travel travel = new CrhTravel(from, to);
sTravelMap.put(key, travel);
return travel;
}
}
}
复制代码
可以看到,在构建对象的时候,首先查看缓存是否存在key的,如果存在,则直接返回,如果不存在就创建一个新的对象并且存入缓存池里面,下面是简单的测试代码:
public class TravelClient {
public static void main(String[] args){
Travel travel= TravelFactory.getTravel("上海","北京");
travel.showDetailInfo(1,300);
Travel trave2= TravelFactory.getTravel("上海","北京");
trave2.showDetailInfo(2,200);
Travel travel3= TravelFactory.getTravel("上海","北京");
travel3.showDetailInfo(3,100);
}
}
复制代码
可以看到,不变的是出发点和终点,而变化的是座位等级和价格,运行结果如图:
1. Message message=Message.obtain();
2. Message message=new Message();
复制代码
我相信大家都是用第一种来获取的,因为里面用到的正是这种享元模式来共享对象的, 来看看第一种的源代码:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
复制代码
可以看到,里面有一个sPool,如果sPool为空,则直接new一个出来,不过sPool类型也是Message,并不是什么池,但是Message的一个字段next居然也是一个Message,可见Message使用的不是我们一般常见的Map,而是一个链表,这个next就指向了下一个Message,如图所示:
可以看到是一个链表链接起来的,next指向了下一个可以用的Message,而最后一个Message的next指向的是null,这样通过next就链接成了一个可以用的对象池了,我们在刚才获取的方法中看到了,在创建的时候如果没有缓冲,是直接new一个出来的,那么是什么时候放进去的呢,是在回收的时候放进去的,下面是代码:
public void recycle() {
if (isInUse()) {//消息还在使用,抛出异常
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
//清空消息,并且添加到消息池中
recycleUnchecked();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
//这里放进池里面了
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
复制代码
直到这里已经明白了,Message是在回收消息的时候清空一些标记为并且把消息放进池里面的,这样的话,当我们再次获取的时候,由于池里面已经有缓存了,因此直接获取缓存,然后在回收的时候又把消息清空掉标记放进池里面,如此反反复复的利用已经存在的对象,利用这个原理,我们可以设计简单的缓存池。
设计简单的缓存池
我们已经知道了Message的缓存了,其实利用这种原理我们也可以设计一个类似的池,类的设计跟Message类似的,下面是代码:
首先是池的设计,考虑到了普通的单线程和多线程
public final class MessagePools {
private MessagePools() throws Exception {
throw new IllegalAccessException("Can not access default");
}
static interface Pool<T> {
/**
* 获取缓存的对象
*
* @return
*/
T acquire();
/**
* 释放消息对象
*
* @param mInstance
* @return
*/
boolean release(T mInstance);
/**
* 是否在缓存池中
*
* @param instance
* @return
*/
boolean isInPool(T instance);
/**
* 获取缓存池的大小
* @return
*/
int getPoolSize();
}
public static class SimplePools<T> implements Pool<T> {
protected final Object[] mPoolObjects;
protected int mPoolSize;
public SimplePools(int poolSize) {
if (poolSize < 0) {
throw new IllegalArgumentException("The poolSize is must >0");
}
mPoolSize = poolSize;
mPoolObjects = new Object[mPoolSize];
}
@Override
public T acquire() {
if (mPoolSize > 0) {
final int poolIndex = mPoolSize - 1;
T mInstance = (T) mPoolObjects[poolIndex];
mPoolObjects[poolIndex] = null;
mPoolSize--;
return mInstance;
}
return null;
}
@Override
public boolean release(T mInstance) {
if (isInPool(mInstance)) {
throw new IllegalStateException("mInstance is Already in the pool!");
}
if (mPoolSize < mPoolObjects.length) {
mPoolObjects[mPoolSize] = mInstance;
mPoolSize++;
return true;
}
return false;
}
@Override
public boolean isInPool(T instance) {
for (int i = 0; i < mPoolSize; i++) {
if (mPoolObjects[i] == instance) {
return true;
}
}
return false;
}
@Override
public int getPoolSize() {
return mPoolSize;
}
}
/**
* 并发情况下的缓存池
* @param <T>
*/
public static class SynchronizedPool<T> extends SimplePools<T>{
private final Object mLock=new Object();
public SynchronizedPool(int poolSize) {
super(poolSize);
}
@Override
public boolean release(T mInstance) {
synchronized (mLock){
return super.release(mInstance);
}
}
@Override
public T acquire() {
synchronized (mLock){
return super.acquire();
}
}
}
}
然后是模拟消息的设计
public class MessageObj implements Serializable {
private static final MessagePools.SynchronizedPool<MessageObj> sPools = new MessagePools.SynchronizedPool<>(30);
//The MessageObj time,The default is currentTime
public long when;
//MessageObj what
public int what;
//Message Obj
public Object obj;
public long getWhen() {
return when;
}
public int getWhat() {
return what;
}
public Object getObj() {
return obj;
}
public static MessageObj obtain(int what, Object obj) {
MessageObj m = obtain();
m.what = what;
m.obj = obj;
return m;
}
private static MessageObj obtain() {
MessageObj messageObj = sPools.acquire();
if (messageObj == null) {
messageObj = new MessageObj();
}
messageObj.when = System.currentTimeMillis();
return messageObj;
}
public void recycle() {
what = 0;
obj = null;
what = 0;
try {
sPools.release(this);
} catch (Exception e) {
e.printStackTrace();
}
}
public MessageObj clone(MessageObj src) {
MessageObj messageObj = new MessageObj();
messageObj.when = src.when;
messageObj.obj = src.obj;
messageObj.what = src.what;
return messageObj;
}
}
复制代码
可以看到,池主要的方法为存入和获取,而模拟消息的方法跟Message获取的方法类似,只是相对简单,字段少了,同样也是在recycler中进行释放然后存储在池里面的,下面来简单测试一下:
Handler handler=new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
if (msg.what==0x1234){
MessageObj messageObj= (MessageObj) msg.obj;
Log.d("[app]","obj="+messageObj.toString());
}
super.handleMessage(msg);
}
};
String test="测试1";
MessageObj messageObj=MessageObj.obtain(0x1234,test);
Message message=handler.obtainMessage(messageObj.getWhat(),messageObj);
handler.sendMessage(message);
运行结果为:obj=MessageObj{when=1513368146482, what=4660, obj=测试1}
复制代码
可以看到程序正常运行,跟Message类似的原理,当然了,池可以添加额外的功能,这个有时间可以去多思考,实际上享元模式更像是一个缓存的对象池,Message并不是享元模式的经典应用,因为并没有内部,外部状态,但我们更应该关注的是对象池的思想来思考和解决问题,灵活应用才是最终的目的。
享元模式总结
享元模式比较简单,但是在一些特定的场合下能发挥重要的作用,可以减少很多不必要对象的创建,降低程序的内存利用,增强了程序的性能,不过也提高了系统的复杂性,特别是内外状态的分离,而外部状态由客户端保存,共享对象读取外部状态的开销可能比较大.
今天的文章就写到这里,感觉大家阅读。