享元设计模式分析以及构建简单对象缓存池

前端外刊评论 2017-12-16

享元设计模式

享元设计模式(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);
    }
}

可以看到,不变的是出发点和终点,而变化的是座位等级和价格,运行结果如图:

享元设计模式分析以及构建简单对象缓存池在第一次用的时候,缓存为空,则直接创建了对象,然后存储起来,第二次以后就直接取出缓存了,这样就可以大量的复用了这些已经存在的对象了,在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的缓存了,其实利用这种原理我们也可以设计一个类似的池,只不过这里的池用Map来保存,类的设计跟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并不是享元模式的经典应用,因为并没有内部,外部状态,但我们更应该关注的是对象池的思想来思考和解决问题,灵活应用才是最终的目的。

享元模式总结

享元模式比较简单,但是在一些特定的场合下能发挥重要的作用,可以减少很多不必要对象的创建,降低程序的内存利用,增强了程序的性能,不过也提高了系统的复杂性,特别是内外状态的分离,而外部状态由客户端保存,共享对象读取外部状态的开销可能比较大.

今天的文章就写到这里,感觉大家阅读。

相关推荐