Java实现一个简单的缓存方法
缓存是在web开发中经常用到的,将程序经常使用到或调用到的对象存在内存中,或者是耗时较长但又不具有实时性的查询数据放入内存中,在一定程度上可以提高性能和效率。下面我实现了一个简单的缓存,步骤如下。
创建缓存对象EntityCache.java
publicclassEntityCache{ /** *保存的数据 */ privateObjectdatas; /** *设置数据失效时间,为0表示永不失效 */ privatelongtimeOut; /** *最后刷新时间 */ privatelonglastRefeshTime; publicEntityCache(Objectdatas,longtimeOut,longlastRefeshTime){ this.datas=datas; this.timeOut=timeOut; this.lastRefeshTime=lastRefeshTime; } publicObjectgetDatas(){ returndatas; } publicvoidsetDatas(Objectdatas){ this.datas=datas; } publiclonggetTimeOut(){ returntimeOut; } publicvoidsetTimeOut(longtimeOut){ this.timeOut=timeOut; } publiclonggetLastRefeshTime(){ returnlastRefeshTime; } publicvoidsetLastRefeshTime(longlastRefeshTime){ this.lastRefeshTime=lastRefeshTime; } }
定义缓存操作接口,ICacheManager.java
publicinterfaceICacheManager{ /** *存入缓存 *@paramkey *@paramcache */ voidputCache(Stringkey,EntityCachecache); /** *存入缓存 *@paramkey *@paramcache */ voidputCache(Stringkey,Objectdatas,longtimeOut); /** *获取对应缓存 *@paramkey *@return */ EntityCachegetCacheByKey(Stringkey); /** *获取对应缓存 *@paramkey *@return */ ObjectgetCacheDataByKey(Stringkey); /** *获取所有缓存 *@paramkey *@return */ MapgetCacheAll(); /** *判断是否在缓存中 *@paramkey *@return */ booleanisContains(Stringkey); /** *清除所有缓存 */ voidclearAll(); /** *清除对应缓存 *@paramkey */ voidclearByKey(Stringkey); /** *缓存是否超时失效 *@paramkey *@return */ booleanisTimeOut(Stringkey); /** *获取所有key *@return */ Set getAllKeys(); }
实现接口ICacheManager,CacheManagerImpl.java
这里我使用了ConcurrentHashMap来保存缓存,本来以为这样就是线程安全的,其实不然,在后面的测试中会发现它并不是线程安全的。
publicclassCacheManagerImplimplementsICacheManager{ privatestaticMapcaches=newConcurrentHashMap (); /** *存入缓存 *@paramkey *@paramcache */ publicvoidputCache(Stringkey,EntityCachecache){ caches.put(key,cache); } /** *存入缓存 *@paramkey *@paramcache */ publicvoidputCache(Stringkey,Objectdatas,longtimeOut){ timeOut=timeOut>0?timeOut:0L; putCache(key,newEntityCache(datas,timeOut,System.currentTimeMillis())); } /** *获取对应缓存 *@paramkey *@return */ publicEntityCachegetCacheByKey(Stringkey){ if(this.isContains(key)){ returncaches.get(key); } returnnull; } /** *获取对应缓存 *@paramkey *@return */ publicObjectgetCacheDataByKey(Stringkey){ if(this.isContains(key)){ returncaches.get(key).getDatas(); } returnnull; } /** *获取所有缓存 *@paramkey *@return */ publicMap getCacheAll(){ returncaches; } /** *判断是否在缓存中 *@paramkey *@return */ publicbooleanisContains(Stringkey){ returncaches.containsKey(key); } /** *清除所有缓存 */ publicvoidclearAll(){ caches.clear(); } /** *清除对应缓存 *@paramkey */ publicvoidclearByKey(Stringkey){ if(this.isContains(key)){ caches.remove(key); } } /** *缓存是否超时失效 *@paramkey *@return */ publicbooleanisTimeOut(Stringkey){ if(!caches.containsKey(key)){ returntrue; } EntityCachecache=caches.get(key); longtimeOut=cache.getTimeOut(); longlastRefreshTime=cache.getLastRefeshTime(); if(timeOut==0||System.currentTimeMillis()-lastRefreshTime>=timeOut){ returntrue; } returnfalse; } /** *获取所有key *@return */ publicSet getAllKeys(){ returncaches.keySet(); } }
CacheListener.java,监听失效数据并移除。
publicclassCacheListener{ Loggerlogger=Logger.getLogger("cacheLog"); privateCacheManagerImplcacheManagerImpl; publicCacheListener(CacheManagerImplcacheManagerImpl){ this.cacheManagerImpl=cacheManagerImpl; } publicvoidstartListen(){ newThread(){ publicvoidrun(){ while(true){ for(Stringkey:cacheManagerImpl.getAllKeys()){ if(cacheManagerImpl.isTimeOut(key)){ cacheManagerImpl.clearByKey(key); logger.info(key+"缓存被清除"); } } } } }.start(); } }
测试类TestCache.java
publicclassTestCache{ Loggerlogger=Logger.getLogger("cacheLog"); /** *测试缓存和缓存失效 */ @Test publicvoidtestCacheManager(){ CacheManagerImplcacheManagerImpl=newCacheManagerImpl(); cacheManagerImpl.putCache("test","test",10*1000L); cacheManagerImpl.putCache("myTest","myTest",15*1000L); CacheListenercacheListener=newCacheListener(cacheManagerImpl); cacheListener.startListen(); logger.info("test:"+cacheManagerImpl.getCacheByKey("test").getDatas()); logger.info("myTest:"+cacheManagerImpl.getCacheByKey("myTest").getDatas()); try{ TimeUnit.SECONDS.sleep(20); }catch(InterruptedExceptione){ e.printStackTrace(); } logger.info("test:"+cacheManagerImpl.getCacheByKey("test")); logger.info("myTest:"+cacheManagerImpl.getCacheByKey("myTest")); } /** *测试线程安全 */ @Test publicvoidtestThredSafe(){ finalStringkey="thread"; finalCacheManagerImplcacheManagerImpl=newCacheManagerImpl(); ExecutorServiceexec=Executors.newCachedThreadPool(); for(inti=0;i<100;i++){ exec.execute(newRunnable(){ publicvoidrun(){ if(!cacheManagerImpl.isContains(key)){ cacheManagerImpl.putCache(key,1,0); }else{ //因为+1和赋值操作不是原子性的,所以把它用synchronize块包起来 synchronized(cacheManagerImpl){ intvalue=(Integer)cacheManagerImpl.getCacheDataByKey(key)+1; cacheManagerImpl.putCache(key,value,0); } } } }); } exec.shutdown(); try{ exec.awaitTermination(1,TimeUnit.DAYS); }catch(InterruptedExceptione1){ e1.printStackTrace(); } logger.info(cacheManagerImpl.getCacheDataByKey(key).toString()); } }
testCacheManager()输出结果如下:
2017-4-1710:33:51io.github.brightloong.cache.TestCachetestCacheManager 信息:test:test 2017-4-1710:33:51io.github.brightloong.cache.TestCachetestCacheManager 信息:myTest:myTest 2017-4-1710:34:01io.github.brightloong.cache.CacheListener$1run 信息:test缓存被清除 2017-4-1710:34:06io.github.brightloong.cache.CacheListener$1run 信息:myTest缓存被清除 2017-4-1710:34:11io.github.brightloong.cache.TestCachetestCacheManager 信息:test:null 2017-4-1710:34:11io.github.brightloong.cache.TestCachetestCacheManager 信息:myTest:null
testThredSafe()输出结果如下(选出了各种结果中的一个举例):
2017-4-1710:35:36io.github.brightloong.cache.TestCachetestThredSafe 信息:96
可以看到并不是预期的结果100,为什么呢?ConcurrentHashMap只能保证单次操作的原子性,但是当复合使用的时候,没办法保证复合操作的原子性,以下代码:
if(!cacheManagerImpl.isContains(key)){ cacheManagerImpl.putCache(key,1,0); }
多线程的时候回重复更新value,设置为1,所以出现结果不是预期的100。所以办法就是在CacheManagerImpl.java中都加上synchronized,但是这样一来相当于操作都是串行,使用ConcurrentHashMap也没有什么意义,不过只是简单的缓存还是可以的。或者对测试方法中的run里面加上synchronized块也行,都是大同小异。更高效的方法我暂时也想不出来,希望大家能多多指教。