定义
享元模式(Flyweight)是对象池的一种实现。享元模式用来尽可能的减少内存使用量,适用于可能存在大量重复对象的场景,来缓存可共享的对象达到对象共享,避免创建过多对象的效果。
享元对象中,可以共享的状态是内部状态,内部状态不会随着环境的变化,不可以共享的状态称为外部状态,外部状态会随着环境变化而变化。享元模式中会创建一个对象容器,经典的享元模式中这个容器是一个Map,它的key是享元对象的内部状态value为享元对象本身。使用者通过内部状态从享元工厂中获取享元对象,如果有缓存则使用缓存,如果没有则创建一个对象存入容器中。
UML图
享元模式,主要有享元工厂和享元对象角色
-
Flyweight
享元对象的抽象类或者接口
-
ConcreteFlyweight
具体的享元对象
-
FlyweightFactory
享元工厂,负责创建享元对象和管理对象池
示例
以购买火车票为例,当我们在网站查询火车票时,一般会输入起始站和终点站查询,这两个值对于一个车票对象来说是不会变的,为内部状态,当然其价格会根据所选铺位的不同而不同,为外部状态。
/**
* 车票类的抽象
*/
public interface Ticket {
//打印车票信息的方法,传入车票的铺位
void printTicketInfo(String bunk);
}
/**
* 享元对象的具体实现
* 其内部状态是起始站from和终点站key
* 外部状态为票价price,其价格根据铺位的不同而不同
*/
public class TrainTicket implements Ticket {
private String from;
private String to;
private int price;
public TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}
@Override
public void printTicketInfo(String bunk) {
price = new Random().nextInt(500);
System.out.println("从" + from + "到" + to + "的" + bunk + "票价为 :" + price + "元");
}
}
/**
* 享元工厂
* 使用ConcurrentHashMap缓存享元对象
* 使用from + "-" + to作为key存储享元对象
*/
public class TicketFactory {
private static ConcurrentHashMap<String,Ticket> sTicketMap = new ConcurrentHashMap<>();
public static Ticket getTicket(String from,String to){
String key = from + "-" + to;
if (sTicketMap.containsKey(key)){
System.out.println("从缓存取" + key);
return sTicketMap.get(key);
}else {
System.out.println("实例化" + key);
Ticket ticket = new TrainTicket(from, to);
sTicketMap.put(key,ticket);
return ticket;
}
}
public static void main(String args[]) {
/**
* 客户端查询调用
*/
Ticket ticket = TicketFactory.getTicket("深圳", "北京");
ticket.printTicketInfo("硬卧");
Ticket ticket1 = TicketFactory.getTicket("深圳", "北京");
ticket1.printTicketInfo("硬座");
Ticket ticket2 = TicketFactory.getTicket("深圳", "北京");
ticket2.printTicketInfo("软卧");
}
实例化深圳-北京
从深圳到北京的硬卧票价为 :154元
从缓存取深圳-北京
从深圳到北京的硬座票价为 :427元
从缓存取深圳-北京
从深圳到北京的软卧票价为 :264元
我们看到示例中只有第一次从工厂中查询时新建了对象,而后面两次都是从缓存中获取的。
Android源码中的享元模式
Android应用是事件驱动的,每个事件都会转化为一个系统消息,即Message。那么应用中很可能就会产生很多的Message对象,而Message对象的获取就是使用了享元模式。在获取Message对象时,我们一般调用Obtain方法
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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();
}
这里看到有m.next,推测使用了链表结构。继续看一下obtain方法涉及到的这几个属性的定义
/ sometimes we store linked lists of these things
/*package*/ Message next;
/** @hide */
public static final Object sPoolSync = new Object();
private static Message sPool;
sPoolSync为Object类型的同步锁,而sPool就是一个Message类型的对象,每个Message对象都有一个同类型的next字段,这个next字段指向下一个可用的Message,最后一个可用的Message的next字段为null。这样所有可用的Message对象就通过next串联成了一个可用的Message池。
这里首先判断链表的表头sPool为空的话就只new一个Message对象返回。当sPool不为空时,将sPool赋值对象赋值给m,sPool = m.next 将sPool对象对象赋值为m的next属性,并将m的next节点置为null,最后将m对象返回,这样就取出了链表的表头。而链表中的对象是什么时候存进去的呢,我们看一下Message对象的回收方法
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
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++;
}
}
}
这里将当前对象的next节点指向sPool,并将sPool指向当前Message对象。即将当前回收的Message对象插入的链表的表头。
Message通过在内部构建一个链表来维护一个被回收的Message对象的对象池子,当用户调用obtain方法时,会首先从池中取,如果池中没有可以复用的对象则创建这个新的Message对象,这个Message对象在使用完后会回收到这个对象池中,以便下一次调用obtain方法时可以复用。