享元設計模式分析以及構建簡單對象緩存池

享元設計模式設計模式

享元設計模式(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);
    }
}
複製代碼

能夠看到,不變的是出發點和終點,而變化的是座位等級和價格,運行結果如圖:

在第一次用的時候,緩存爲空,則直接建立了對象,而後存儲起來,第二次之後就直接取出緩存了,這樣就能夠大量的複用了這些已經存在的對象了,在Android中,Message做爲消息機制的重要成員,這裏簡單分析一下,咱們知道,通常在Android獲取一個消息對象的時候,通常有2種寫法

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並非享元模式的經典應用,由於並無內部,外部狀態,但咱們更應該關注的是對象池的思想來思考和解決問題,靈活應用纔是最終的目的。

享元模式總結

享元模式比較簡單,可是在一些特定的場合下能發揮重要的做用,能夠減小不少沒必要要對象的建立,下降程序的內存利用,加強了程序的性能,不過也提升了系統的複雜性,特別是內外狀態的分離,而外部狀態由客戶端保存,共享對象讀取外部狀態的開銷可能比較大.

今天的文章就寫到這裏,感受你們閱讀。

相關文章
相關標籤/搜索