缓存是在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块也行,都是大同小异。更高效的方法我暂时也想不出来,希望大家能多多指教。
原文链接:http://www.jianshu.com/p/bd8dc4a8bbc7#