BrightLoong's Blog

Java实现一个简单的缓存

cache
  缓存是在web开发中经常用到的,将程序经常使用到或调用到的对象存在内存中,或者是耗时较长但又不具有实时性的查询数据放入内存中,在一定程度上可以提高性能和效率。下面我实现了一个简单的缓存,步骤如下。

创建缓存对象EntityCache.java

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
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

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
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来保存缓存,本来以为这样就是线程安全的,其实不然,在后面的测试中会发现它并不是线程安全的。

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
107
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,监听失效数据并移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

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
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()输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
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()输出结果如下(选出了各种结果中的一个举例):

1
2
2017-4-17 10:35:36 io.github.brightloong.cache.TestCache testThredSafe
信息: 96

可以看到并不是预期的结果100,为什么呢?ConcurrentHashMap只能保证单次操作的原子性,但是当复合使用的时候,没办法保证复合操作的原子性,以下代码:

1
2
3
if (!cacheManagerImpl.isContains(key)) {
cacheManagerImpl.putCache(key, 1, 0);
}

多线程的时候回重复更新value,设置为1,所以出现结果不是预期的100。所以办法就是在CacheManagerImpl.java中都加上synchronized,但是这样一来相当于操作都是串行,使用ConcurrentHashMap也没有什么意义,不过只是简单的缓存还是可以的。或者对测试方法中的run里面加上synchronized块也行,都是大同小异。更高效的方法我暂时也想不出来,希望大家能多多指教。

坚持原创技术分享,您的支持将鼓励我继续创作!