Java实现一个简单的缓存

cache

阅读原文请访问我的博客BrightLoong's Blog
  缓存是在web开发中经常用到的,将程序经常使用到或调用到的对象存在内存中,或者是耗时较长但又不具有实时性的查询数据放入内存中,在一定程度上可以提高性能和效率。下面我实现了一个简单的缓存,步骤如下。

创建缓存对象EntityCache.java

public class EntityCache {
    /**
     * 保存的数据
     */
    private  Object datas;
    
    /**
     * 设置数据失效时间,为0表示永不失效
     */
    private  long timeOut;
    
    /**
     * 最后刷新时间
     */
    private  long lastRefeshTime;
    
    public EntityCache(Object datas, long timeOut, long lastRefeshTime) {
        this.datas = datas;
        this.timeOut = timeOut;
        this.lastRefeshTime = lastRefeshTime;
    }
    public Object getDatas() {
        return datas;
    }
    public void setDatas(Object datas) {
        this.datas = datas;
    }
    public long getTimeOut() {
        return timeOut;
    }
    public void setTimeOut(long timeOut) {
        this.timeOut = timeOut;
    }
    public long getLastRefeshTime() {
        return lastRefeshTime;
    }
    public void setLastRefeshTime(long lastRefeshTime) {
        this.lastRefeshTime = lastRefeshTime;
    }
    
    
}

定义缓存操作接口,ICacheManager.java

public interface ICacheManager {
    /**
     * 存入缓存
     * @param key
     * @param cache
     */
    void putCache(String key, EntityCache cache);
    
    /**
     * 存入缓存
     * @param key
     * @param cache
     */
    void putCache(String key, Object datas, long timeOut);
    
    /**
     * 获取对应缓存
     * @param key
     * @return
     */
    EntityCache getCacheByKey(String key);
    
    /**
     * 获取对应缓存
     * @param key
     * @return
     */
    Object getCacheDataByKey(String key);
    
    /**
     * 获取所有缓存
     * @param key
     * @return
     */
    Map<String, EntityCache> getCacheAll();
    
    /**
     * 判断是否在缓存中
     * @param key
     * @return
     */
    boolean isContains(String key);
    
    /**
     * 清除所有缓存
     */
    void clearAll();
    
    /**
     * 清除对应缓存
     * @param key
     */
    void clearByKey(String key);
    
    /**
     * 缓存是否超时失效
     * @param key
     * @return
     */
    boolean isTimeOut(String key);
    
    /**
     * 获取所有key
     * @return
     */
    Set<String> getAllKeys();
}

实现接口ICacheManager,CacheManagerImpl.java

  这里我使用了ConcurrentHashMap来保存缓存,本来以为这样就是线程安全的,其实不然,在后面的测试中会发现它并不是线程安全的。

public class CacheManagerImpl implements ICacheManager {
    private static Map<String, EntityCache> caches = new ConcurrentHashMap<String, EntityCache>();

    /**
     * 存入缓存
     * @param key
     * @param cache
     */
    public void putCache(String key, EntityCache cache) {
        caches.put(key, cache);
    }
    
    /**
     * 存入缓存
     * @param key
     * @param cache
     */
    public void putCache(String key, Object datas, long timeOut) {
        timeOut = timeOut > 0 ? timeOut : 0L;
        putCache(key, new EntityCache(datas, timeOut, System.currentTimeMillis()));
    }
    
    /**
     * 获取对应缓存
     * @param key
     * @return
     */
    public EntityCache getCacheByKey(String key) {
        if (this.isContains(key)) {
            return caches.get(key);
        }
        return null;
    }
    
    /**
     * 获取对应缓存
     * @param key
     * @return
     */
    public Object getCacheDataByKey(String key) {
        if (this.isContains(key)) {
            return caches.get(key).getDatas();
        }
        return null;
    }

    /**
     * 获取所有缓存
     * @param key
     * @return
     */
    public Map<String, EntityCache> getCacheAll() {
        return caches;
    }
    
    /**
     * 判断是否在缓存中
     * @param key
     * @return
     */
    public boolean isContains(String key) {
        return caches.containsKey(key);
    }

    /**
     * 清除所有缓存
     */
    public void clearAll() {
        caches.clear();
    }
    
    /**
     * 清除对应缓存
     * @param key
     */
    public void clearByKey(String key) {
        if (this.isContains(key)) {
            caches.remove(key);
        }
    }
    
    /**
     * 缓存是否超时失效
     * @param key
     * @return
     */
    public boolean isTimeOut(String key) {
        if (!caches.containsKey(key)) {
            return true;
        }
        EntityCache cache = caches.get(key);
        long timeOut = cache.getTimeOut();
        long lastRefreshTime = cache.getLastRefeshTime();
        if (timeOut == 0 || System.currentTimeMillis() - lastRefreshTime >= timeOut) {
            return true;
        }
        return false;
    }

    /**
     * 获取所有key
     * @return
     */
    public Set<String>  getAllKeys() {
        return caches.keySet();
    }
}

CacheListener.java,监听失效数据并移除。

public class CacheListener{
    Logger logger = Logger.getLogger("cacheLog");
    private CacheManagerImpl cacheManagerImpl;
    public CacheListener(CacheManagerImpl cacheManagerImpl) {
        this.cacheManagerImpl = cacheManagerImpl;
    }
    
    public void startListen() {
        new Thread(){
            public void run() {
                while (true) {
                    for(String key : cacheManagerImpl.getAllKeys()) {
                        if (cacheManagerImpl.isTimeOut(key)) {
                         cacheManagerImpl.clearByKey(key);
                         logger.info(key + "缓存被清除");
                     }
                    } 
                }
            }  
        }.start();
    
    }
}

测试类TestCache.java

public class TestCache {
    Logger logger = Logger.getLogger("cacheLog");
    /**
     * 测试缓存和缓存失效
     */
    @Test
    public void testCacheManager() {
        CacheManagerImpl cacheManagerImpl = new CacheManagerImpl();
        cacheManagerImpl.putCache("test", "test", 10 * 1000L);
        cacheManagerImpl.putCache("myTest", "myTest", 15 * 1000L);
        CacheListener cacheListener = new CacheListener(cacheManagerImpl);
        cacheListener.startListen();
        logger.info("test:" + cacheManagerImpl.getCacheByKey("test").getDatas());
        logger.info("myTest:" + cacheManagerImpl.getCacheByKey("myTest").getDatas());
        try {
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("test:" + cacheManagerImpl.getCacheByKey("test"));
        logger.info("myTest:" + cacheManagerImpl.getCacheByKey("myTest"));
    }
    
    /**
     * 测试线程安全
     */
    @Test
    public void testThredSafe() {
        final String key = "thread";
        final CacheManagerImpl cacheManagerImpl = new CacheManagerImpl();
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            exec.execute(new Runnable() {
                public void run() {
                        if (!cacheManagerImpl.isContains(key)) {
                            cacheManagerImpl.putCache(key, 1, 0);
                        } else {
                            //因为+1和赋值操作不是原子性的,所以把它用synchronize块包起来
                            synchronized (cacheManagerImpl) {
                               int value = (Integer) cacheManagerImpl.getCacheDataByKey(key) + 1; 
                               cacheManagerImpl.putCache(key,value , 0);
                            }
                        }
                }
            });
        }
        exec.shutdown();  
        try {
            exec.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }  
        
        logger.info(cacheManagerImpl.getCacheDataByKey(key).toString());
    }
}

testCacheManager()输出结果如下:

2017-4-17 10:33:51 io.github.brightloong.cache.TestCache testCacheManager
信息: test:test
2017-4-17 10:33:51 io.github.brightloong.cache.TestCache testCacheManager
信息: myTest:myTest
2017-4-17 10:34:01 io.github.brightloong.cache.CacheListener$1 run
信息: test缓存被清除
2017-4-17 10:34:06 io.github.brightloong.cache.CacheListener$1 run
信息: myTest缓存被清除
2017-4-17 10:34:11 io.github.brightloong.cache.TestCache testCacheManager
信息: test:null
2017-4-17 10:34:11 io.github.brightloong.cache.TestCache testCacheManager
信息: myTest:null

testThredSafe()输出结果如下(选出了各种结果中的一个举例):

2017-4-17 10:35:36 io.github.brightloong.cache.TestCache testThredSafe
信息: 96

可以看到并不是预期的结果100,为什么呢?ConcurrentHashMap只能保证单次操作的原子性,但是当复合使用的时候,没办法保证复合操作的原子性,以下代码:

if (!cacheManagerImpl.isContains(key)) {
                            cacheManagerImpl.putCache(key, 1, 0);
                        }

多线程的时候回重复更新value,设置为1,所以出现结果不是预期的100。所以办法就是在CacheManagerImpl.java中都加上synchronized,但是这样一来相当于操作都是串行,使用ConcurrentHashMap也没有什么意义,不过只是简单的缓存还是可以的。或者对测试方法中的run里面加上synchronized块也行,都是大同小异。更高效的方法我暂时也想不出来,希望大家能多多指教。

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

推荐阅读更多精彩内容