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
*/
SetgetAllKeys();
}
实现接口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
*/
publicMapgetCacheAll(){
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
*/
publicSetgetAllKeys(){
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块也行,都是大同小异。更高效的方法我暂时也想不出来,希望大家能多多指教。