享元設計模式設計模式
享元設計模式(Flyweight Pattern)是23種設計模式中普遍引用的其中一種,主要用在構建緩存對象的時候用到,不論是在Java,仍是在Android中,都不可或缺,好比咱們常見String字符串以及自定義的View中的TypeArray,線程池或者Message等等都有用到享元模式,共同的特色都是複用了已經存在的細粒度對象,而且在對象不須要的時候回收,方便再次使用,減小系統的消耗。緩存
享元模式圖bash
●FlyWeight 享元接口或者(抽象享元類),定義共享接口多線程
●ConcreteFlyWeight 具體享元類,該類實例將實現共享併發
●FlyWeightFactory 享元工廠類,控制實例的建立和共享app
●ConcreteConpositeFlyweight 它所表明的對象是不能夠共享的,而且能夠分解成爲多個單純享元對象的組合。ide
享元模式內部狀態VS外部狀態函數
●內部狀態是存儲在享元對象內部,通常在構造函數的時候肯定了,而且不會隨環境改變而改變的狀態,所以內部狀態能夠共享。oop
●外部狀態是隨環境改變而改變,外部狀態在須要使用時經過客戶端傳入享元對象,因爲是隨環境而改變,所以內部沒法存儲,必須由客戶端保存。性能
舉個例子,從上海到北京的高鐵來講,這裏的內部狀態是出發點上海和終點北京組成,這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並非享元模式的經典應用,由於並無內部,外部狀態,但咱們更應該關注的是對象池的思想來思考和解決問題,靈活應用纔是最終的目的。
享元模式總結
享元模式比較簡單,可是在一些特定的場合下能發揮重要的做用,能夠減小不少沒必要要對象的建立,下降程序的內存利用,加強了程序的性能,不過也提升了系統的複雜性,特別是內外狀態的分離,而外部狀態由客戶端保存,共享對象讀取外部狀態的開銷可能比較大.
今天的文章就寫到這裏,感受你們閱讀。