享元模式-构建简易缓存池

本篇一起来写一个简易的缓存池,就是对象缓存池,就是设计模式中的享元模式。其实support包(Androidx一样也有)中提供了一个叫Pools的类,可能不是很多小伙伴知道,我也是偶然发现Glide使用它来做对象缓存复用,所以顺手看了一下源码,代码不多,但是很精华,我看完后手写了一遍源码,更改了获取和回收的方法名(相对原方法名更加容易懂一些),加了一些注释。

Pool池接口

首先我们先定义Pool池接口,目的是为了给后续不同策略的缓存池做一个统一的获取、回收方法,面向接口编程!

  • obtain()获取一个缓存对象,如果没有缓存对象复用,则返回null。
  • recycle(),回收一个对象到缓存池中,如果对象池已满,则不会回收,返回值,如果返回true,则回收成功,返回false,代表回收失败(回收时,池子已满则会出现)。
public class CachePool {
    private CachePool() {
    }

    /**
     * 池接口
     */
    public interface Pool<T> {
        /**
         * 获取一个缓存对象,如果没有缓存对象复用,则返回null
         */
        T obtain();
    
        /**
         * 回收缓存对象到池子里,如果对象池已满,则不会回收
         *
         * @return 返回true,则回收成功,返回false,代表回收失败(回收时,池子已满则会出现)
         */
        boolean recycle(T instance);
    }
}

简单的对象池

Pool只是一个接口,而SimpleCachePool为Pool接口的简单实现,它是不支持在多线程中保证线程安全的池子。如果不需要在多线程中使用,则使用它就可以。

如果需要多线程中保证多线程安全,则需要使用下面的SynchronizedCachePool。

  • 构造时传入最大缓存大小,例如10,当缓存个数达到最大值,则不会将对象放入缓存池中缓存。
/**
 * 简单的对象池
 */
public static class SimpleCachePool<T> implements Pool<T> {
    /**
     * 对象池
     */
    private Object[] mPool;
    /**
     * 对象池大小
     */
    private int mPoolSize;

    /**
     * @param maxPoolSize 最大池容量
     */
    public SimpleCachePool(int maxPoolSize) {
        if (maxPoolSize <= 0) {
            throw new IllegalArgumentException("对象池大小必须大于0");
        }
        mPool = new Object[maxPoolSize];
    }

    @SuppressWarnings("unchecked")
    @Override
    public T obtain() {
        //对象池中有对象可以复用时,才拿取最后一个对象返回
        if (mPoolSize > 0) {
            int lastIndex = mPoolSize - 1;
            T object = (T) mPool[lastIndex];
            //将对象从池子中移除
            mPool[lastIndex] = null;
            //同步池子大小
            mPoolSize--;
            return object;
        }
        return null;
    }

    @Override
    public boolean recycle(T instance) {
        //判断传入的对象是否在池子中,正常调用obtain()获取的对象会从池子中移除
        //如果判断还在池子中则可能是已经被回收过了,多次调用回收
        if (isInPool(instance)) {
            throw new IllegalStateException("对象已经在池子中了,请确保没有多次调用recycle()回收同一个对象");
        }
        //判断池子是否已满,如果满了,则不放入池子
        if (mPoolSize < mPool.length) {
            //将对象放回池子中复用
            mPool[mPoolSize] = instance;
            //同步池子大小
            mPoolSize++;
            return true;
        }
        return false;
    }

    /**
     * 判断对象是否在池子中
     *
     * @return 返回true则代表对象在池子中,返回false则不在
     */
    private boolean isInPool(T target) {
        for (Object object : mPool) {
            if (object == target) {
                return true;
            }
        }
        return false;
    }
}

支持多线程同步的缓存池

SynchronizedCachePool继承于SimpleCachePool,并在它的obtain()和recycle()中加入synchronized同步锁来保证线程安全。

/**
 * 同步存取的缓存池,一般用于多线程需要保证线程安全的情况
 */
public static class SynchronizedCachePool<T> extends SimpleCachePool<T> {
    /**
     * 对象锁
     */
    private final Object mLock = new Object();

    public SynchronizedCachePool(int maxPoolSize) {
        super(maxPoolSize);
    }

    @Override
    public T obtain() {
        synchronized (mLock) {
            return super.obtain();
        }
    }

    @Override
    public boolean recycle(T instance) {
        synchronized (mLock) {
            return super.recycle(instance);
        }
    }
}

简单使用

使用则很简单了,使用obtain()方法获取缓存对象,如果获取不到,则自己创建,经过使用后,通过recycle()将对象回收即可。如果需要使用SynchronizedCachePool,则创建SynchronizedCachePool即可。

//1、创建缓存池
val cachePool = CachePool.SimpleCachePool<MessageModel>(10)
//2、先从缓存池中获取,如果没获取到则创建
var model: MessageModel? = cachePool.obtain()
if (model == null) {
    model = ToiletModel()
}

...一轮使用

//3、进行回收
cachePool.recycle(model)

完整代码(直接拷贝去用)

public class CachePool {
    private CachePool() {
    }

    /**
     * 池接口
     */
    public interface Pool<T> {
        /**
         * 获取一个缓存对象,如果没有缓存对象复用,则返回null
         */
        T obtain();

        /**
         * 回收缓存对象到池子里,如果对象池已满,则不会回收
         *
         * @return 返回true,则回收成功,返回false,代表回收失败(回收时,池子已满则会出现)
         */
        boolean recycle(T instance);
    }

    /**
     * 简单的对象池
     */
    public static class SimpleCachePool<T> implements Pool<T> {
        /**
         * 对象池
         */
        private Object[] mPool;
        /**
         * 对象池大小
         */
        private int mPoolSize;

        /**
         * @param maxPoolSize 最大池容量
         */
        public SimpleCachePool(int maxPoolSize) {
            if (maxPoolSize <= 0) {
                throw new IllegalArgumentException("对象池大小必须大于0");
            }
            mPool = new Object[maxPoolSize];
        }

        @SuppressWarnings("unchecked")
        @Override
        public T obtain() {
            //对象池中有对象可以复用时,才拿取最后一个对象返回
            if (mPoolSize > 0) {
                int lastIndex = mPoolSize - 1;
                T object = (T) mPool[lastIndex];
                //将对象从池子中移除
                mPool[lastIndex] = null;
                //同步池子大小
                mPoolSize--;
                return object;
            }
            return null;
        }

        @Override
        public boolean recycle(T instance) {
            //判断传入的对象是否在池子中,正常调用obtain()获取的对象会从池子中移除
            //如果判断还在池子中则可能是已经被回收过了,多次调用回收
            if (isInPool(instance)) {
                throw new IllegalStateException("对象已经在池子中了,请确保没有多次调用recycle()回收同一个对象");
            }
            //判断池子是否已满,如果满了,则不放入池子
            if (mPoolSize < mPool.length) {
                //将对象放回池子中复用
                mPool[mPoolSize] = instance;
                //同步池子大小
                mPoolSize++;
                return true;
            }
            return false;
        }

        /**
         * 判断对象是否在池子中
         *
         * @return 返回true则代表对象在池子中,返回false则不在
         */
        private boolean isInPool(T target) {
            for (Object object : mPool) {
                if (object == target) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * 同步存取的缓存池,一般用于多线程需要保证线程安全的情况
     */
    public static class SynchronizedCachePool<T> extends SimpleCachePool<T> {
        /**
         * 对象锁
         */
        private final Object mLock = new Object();

        public SynchronizedCachePool(int maxPoolSize) {
            super(maxPoolSize);
        }

        @Override
        public T obtain() {
            synchronized (mLock) {
                return super.obtain();
            }
        }

        @Override
        public boolean recycle(T instance) {
            synchronized (mLock) {
                return super.recycle(instance);
            }
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352