Java实现一个简单的缓存

来源:互联网 发布:我是大主宰服务端源码 编辑:程序博客网 时间:2024/05/17 03:14

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:test2017-4-17 10:33:51 io.github.brightloong.cache.TestCache testCacheManager信息: myTest:myTest2017-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:null2017-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块也行,都是大同小异。更高效的方法我暂时也想不出来,希望大家能多多指教。

原创粉丝点击