阅读原文请访问我的博客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;
-
}
-
-
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
定义缓存操作接口,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();
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
实现接口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();
-
}
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
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();
-
-
}
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
测试类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());
-
}
-
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
testThredSafe()输出结果如下(选出了各种结果中的一个举例):
-
2017
-4-17 10
:35
:36
io
.github
.brightloong
.cache
.TestCache
testThredSafe
-
信息: 96
- 1
可以看到并不是预期的结果100,为什么呢?ConcurrentHashMap只能保证单次操作的原子性,但是当复合使用的时候,没办法保证复合操作的原子性,以下代码:
-
if (!cacheManagerImpl.isContains(key)) {
-
cacheManagerImpl.putCache(key,
1,
0);
-
}
- 1
- 2
多线程的时候回重复更新value,设置为1,所以出现结果不是预期的100。所以办法就是在CacheManagerImpl.java中都加上synchronized,但是这样一来相当于操作都是串行,使用ConcurrentHashMap也没有什么意义,不过只是简单的缓存还是可以的。或者对测试方法中的run里面加上synchronized块也行,都是大同小异。更高效的方法我暂时也想不出来,希望大家能多多指教。