
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");/*** 测试缓存和缓存失效*/@Testpublic 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"));}/*** 测试线程安全*/@Testpublic 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块也行,都是大同小异。更高效的方法我暂时也想不出来,希望大家能多多指教。