享元模式(Flyweight)
此篇文档分成两部分,一、通过一个简单的案例实现去理解此模式的精髓;二、介绍使用了享元模式的Handler消息机制—Message。
概念:使用共享对象可以有效的支持大量的细粒度的对象
(细粒度对象:将业务对象加以细分,从而得到更加科学合理的对象模型)
使用场景:系统中存在大量的相似对象;需要缓冲池的场景
享元模式图示.
根据图示,下面我们通过一个简单的案例实现理解模式的精髓。
一、实现场景描述:从深圳-海口的机票,肯定有N人都在向服务器发出请求,查询一些详细的信息,且对于每一个请求,服务器必须做出回答。假设当数以万人不间断地在请求数据时,如果每次都重新创建一个对象返回查询结果,那么必然会造成大量对象的创建、销毁,使得GC频繁操作,内存占用可想而知。下面通过享元模式实现,在用户查询时优先使用缓存的对象,没有缓存才创建。
抽象基类(Flyweight)
public interface ITickets {
//FlyWeight,享元对象的抽象基类/接口
public void showTickets(String bunk);
}
实现类(ConcreateFlyweight)
public class F implements ITickets { //具体的享元对象
public String from; //始发地
public String to; //目的地
public String bunk;//舱位类型
public int price; //价格
F(String from ,String to){ //构造函数,主要就是初始化变量
this.from=from;
this.to = to;
} //享元对象中,可以共享的成为“内部状态”,反之,“外部状态”
@Override
public void showTickets(String bunk) {
price= new Random().nextInt(10000); //Note:价格是变化的,不能共享
System.out.println("先生/女士,您好,您购买的从"+from +"到"+to+"的"+bunk+经出票成功!");"飞机票"+",价格:"+price+",已
}
}
管理对象类
public class FlyTicket {
static Map<String ,ITickets> sTicketMp = new ConcurrentHashMap<String ,ITickets>();
public static ITickets getTicket(String from ,String to){
String key = from +"-"+to;
if(sTicketMp.containsKey(key)) //如果容器中存在此对象,直接复用!
{
System.out.println("使用的是缓存对象:"+key);
return sTicketMp.get(key);
}
else
{
System.out.println("创建对象:" +key );
ITickets f = new F(from,to); //对象的创建
sTicketMp.put(key,f);
return f;
}
}
}
Note:在享元模式中,它会建立一个Map对象容器(key-value),key是享元对象的内部状态,value是享元对象本身。这里通过ConcurrentHashMap实现,它在线程安全的基础上提供了更好的写并发能力(适合高并发的场景)。在这里,如果缓存的Map中存在对象就直接使用,否则会创建。避免了重复对象的存在。
测试类
public class Test {
public static void main(String args[]){
ITickets t1 = FlyTicket.getTicket("深圳","海口");
t1.showTickets("头等舱");
ITickets t2 = FlyTicket.getTicket("深圳","海口");
t2.showTickets("商务舱");
ITickets t3 = FlyTicket.getTicket("深圳","海口");
t3.showTickets("经济舱");
}
}
Note:运行结果如下图所示
从上图中也可以看出来它只有在第一次查询的时候创建了对象,之后类似的对象都是直接从Map容器中取出来。
Android源码中的享元模式—Handler消息机制
Handler工作原理示意图.
由上述的示意图可知,Handler的工作原理大致为:(Android中不允许在UI线程/即主线程中更新UI界面。但是UI在其他的线程更新会抛出异常,这就产生了冲突!因此,一般只能在子线程完成UI更新的耗时工作之后,通过Handler对象将结果传递给UI线程进行更新。其实可以理解成:这是一个耗时的操作,不能在主线程完成!~)。
注意这里的Handler只能在主线程中创建。因为如果在其他线程中创建Handler对象,就默认持有一个其他线程的Looper对象;其次,Handler又只能访问主线程中的Looper。因此会产生消息无法传递、获取的问题!
MessageQueue消息队列是一个单链表的数据结构,不能被它的字面意思给误导了,它并不是一个队列。链表在数据的插入删除上的空间复杂度、时间复杂度相对于其他数据结构来说,略有优势。
Looper轮训器,它会不断的从消息队列中不断“询问”,当有新的消息产生的时候,立马将新消息发送给Handler。
Thread将在子线程中处理完成的结果封装在Message对象,被上述的Looper检测到,由Handler发送到主线程。
问题的引出:上述大致就是Handler的工作原理。我们很容易发现,在这过程中,消息对象Message的创建是可能会是最频繁的。那么在消息处理这一环节,源码是怎么实现的呢?下面开始我们一探究竟!(重点关注的是Message消息对象!)
在子线程中完成耗时的处理之后,我们一般这样封装结果:
Message msg = Message.obtain();
msg.obj = obj ; //obj为结果的对象
msg.what = 1 ; //结果对象的标识
mHandler.sendmessage(msg); //这里发送的方式也有多种
在主线程中的handlerMessage()方法接收封装的结果:
Object obj = msg.obj;
If(msg.what == 1)
{
..............do something to update UI ..............
}
1、Message.obtain()
从上述的例子中我们可以看出来,获取Message消息对象,并没有通过new,而是直接通过Message.obtain()的方式。源码如下所示:
Path:Message.java/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) { //如果不为空返回message对象
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message(); //否则创建对象返回
}
Note: Message类实现Parcelable接口。熟悉的“Pool”,池!(享元模式中的Map对象容器?)我也不确定,继续跟代码吧~
sPoolSync是一个普通的对象 private static final Object sPoolSync = new Object();它在这里的作用就是当获取Message对象时进行同步锁。
private static Message sPool;从它的声明中可以看出来sPool是一个Message对象。
// sometimes we store linked lists of these things
Message next; //从上述的注释中也可以看出来,我们常说的MessageQueue使用的是list数据结构。这里的next相当于链表中的指针域,指向下一个Message。因此,所有可用的Message对象就是通过next串连成一个可用的Message池。
综上所述,obtain()就是通过next获取,存储对象。使用了list数据结构。且从代码的整体来猜测,当“池”中不为空的时候,即存在该对象的时候直接返回,否则会创建。这似乎看得有点像享元模式中防止创建过多对象的理念了。那么pool池中又是如何去判断消息存在与否呢?继续看那下面的源码.......
2、recycle( )
当我们创建消息的时候,它不会把Message对象放到Pool池中,在回收(并不是GC操作)该对象时才将对象添加到list中。
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");
}
return; //判断是否在使用
}
recycleUnchecked();
}
Note:如果消息对象在使用,会抛出IllegalStateException异常;否则,调用recycleUnchecked()函数。
recycleUnchecked()
void recycleUnchecked() {
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) { //MAX_POOL_SIZE = 50
next = sPool;
sPool = this;
sPoolSize++;
}
}
Note: 上述函数会将Message消息回收到“池”中,即链表list中。且还会进行判断标记是否使用过该消息,新消息会被插到链表的表头, sPoolSize++;当然,如果链表中已经存在此消息对象的时候,直接复用, sPoolSize--,返回给了调用obtain函数的客户端程序。
链表中消息结构示意图:
总结:这里的Message消息对象管理虽然并不能一一对应上享元模式中的抽象基类、基类的实现类、对象的管理类。但整体说来,Message可以说是采用了享元模式的精髓!因为,Message通过在内部构建一个链表来维护一个被回收(已经使用过的消息,并不是GC操作)的Message对象的对象池。当用户调用obtain()的时候,会优先从池中取,如果没有则创建(其实也有点类似于Activity启动模式中的栈内复用模式—singleTask思想!)。新创建的对象在被使用完之后会被回收到池中,以备下次复用。