Java实现一个简单的缓存

cache.jpg
cache


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

创建缓存对象EntityCache.java

  1. public class EntityCache {
  2. /**
  3. * 保存的数据
  4. */
  5. private Object datas;
  6. /**
  7. * 设置数据失效时间,为0表示永不失效
  8. */
  9. private long timeOut;
  10. /**
  11. * 最后刷新时间
  12. */
  13. private long lastRefeshTime;
  14. public EntityCache(Object datas, long timeOut, long lastRefeshTime) {
  15. this.datas = datas;
  16. this.timeOut = timeOut;
  17. this.lastRefeshTime = lastRefeshTime;
  18. }
  19. public Object getDatas() {
  20. return datas;
  21. }
  22. public void setDatas(Object datas) {
  23. this.datas = datas;
  24. }
  25. public long getTimeOut() {
  26. return timeOut;
  27. }
  28. public void setTimeOut(long timeOut) {
  29. this.timeOut = timeOut;
  30. }
  31. public long getLastRefeshTime() {
  32. return lastRefeshTime;
  33. }
  34. public void setLastRefeshTime(long lastRefeshTime) {
  35. this.lastRefeshTime = lastRefeshTime;
  36. }
  37. }
  • 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

定义缓存操作接口,ICacheManager.java

  1. public interface ICacheManager {
  2. /**
  3. * 存入缓存
  4. * @param key
  5. * @param cache
  6. */
  7. void putCache(String key, EntityCache cache);
  8. /**
  9. * 存入缓存
  10. * @param key
  11. * @param cache
  12. */
  13. void putCache(String key, Object datas, long timeOut);
  14. /**
  15. * 获取对应缓存
  16. * @param key
  17. * @return
  18. */
  19. EntityCache getCacheByKey(String key);
  20. /**
  21. * 获取对应缓存
  22. * @param key
  23. * @return
  24. */
  25. Object getCacheDataByKey(String key);
  26. /**
  27. * 获取所有缓存
  28. * @param key
  29. * @return
  30. */
  31. Map<String, EntityCache> getCacheAll();
  32. /**
  33. * 判断是否在缓存中
  34. * @param key
  35. * @return
  36. */
  37. boolean isContains(String key);
  38. /**
  39. * 清除所有缓存
  40. */
  41. void clearAll();
  42. /**
  43. * 清除对应缓存
  44. * @param key
  45. */
  46. void clearByKey(String key);
  47. /**
  48. * 缓存是否超时失效
  49. * @param key
  50. * @return
  51. */
  52. boolean isTimeOut(String key);
  53. /**
  54. * 获取所有key
  55. * @return
  56. */
  57. Set<String> getAllKeys();
  58. }
  • 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

实现接口ICacheManager,CacheManagerImpl.java

  这里我使用了ConcurrentHashMap来保存缓存,本来以为这样就是线程安全的,其实不然,在后面的测试中会发现它并不是线程安全的。

  1. public class CacheManagerImpl implements ICacheManager {
  2. private static Map<String, EntityCache> caches = new ConcurrentHashMap<String, EntityCache>();
  3. /**
  4. * 存入缓存
  5. * @param key
  6. * @param cache
  7. */
  8. public void putCache(String key, EntityCache cache) {
  9. caches.put(key, cache);
  10. }
  11. /**
  12. * 存入缓存
  13. * @param key
  14. * @param cache
  15. */
  16. public void putCache(String key, Object datas, long timeOut) {
  17. timeOut = timeOut > 0 ? timeOut : 0L;
  18. putCache(key, new EntityCache(datas, timeOut, System.currentTimeMillis()));
  19. }
  20. /**
  21. * 获取对应缓存
  22. * @param key
  23. * @return
  24. */
  25. public EntityCache getCacheByKey(String key) {
  26. if ( this.isContains(key)) {
  27. return caches.get(key);
  28. }
  29. return null;
  30. }
  31. /**
  32. * 获取对应缓存
  33. * @param key
  34. * @return
  35. */
  36. public Object getCacheDataByKey(String key) {
  37. if ( this.isContains(key)) {
  38. return caches.get(key).getDatas();
  39. }
  40. return null;
  41. }
  42. /**
  43. * 获取所有缓存
  44. * @param key
  45. * @return
  46. */
  47. public Map<String, EntityCache> getCacheAll() {
  48. return caches;
  49. }
  50. /**
  51. * 判断是否在缓存中
  52. * @param key
  53. * @return
  54. */
  55. public boolean isContains(String key) {
  56. return caches.containsKey(key);
  57. }
  58. /**
  59. * 清除所有缓存
  60. */
  61. public void clearAll() {
  62. caches.clear();
  63. }
  64. /**
  65. * 清除对应缓存
  66. * @param key
  67. */
  68. public void clearByKey(String key) {
  69. if ( this.isContains(key)) {
  70. caches.remove(key);
  71. }
  72. }
  73. /**
  74. * 缓存是否超时失效
  75. * @param key
  76. * @return
  77. */
  78. public boolean isTimeOut(String key) {
  79. if (!caches.containsKey(key)) {
  80. return true;
  81. }
  82. EntityCache cache = caches.get(key);
  83. long timeOut = cache.getTimeOut();
  84. long lastRefreshTime = cache.getLastRefeshTime();
  85. if (timeOut == 0 || System.currentTimeMillis() - lastRefreshTime >= timeOut) {
  86. return true;
  87. }
  88. return false;
  89. }
  90. /**
  91. * 获取所有key
  92. * @return
  93. */
  94. public Set<String> getAllKeys() {
  95. return caches.keySet();
  96. }
  97. }
  • 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

CacheListener.java,监听失效数据并移除。

  1. public class CacheListener{
  2. Logger logger = Logger.getLogger( "cacheLog");
  3. private CacheManagerImpl cacheManagerImpl;
  4. public CacheListener(CacheManagerImpl cacheManagerImpl) {
  5. this.cacheManagerImpl = cacheManagerImpl;
  6. }
  7. public void startListen() {
  8. new Thread(){
  9. public void run() {
  10. while ( true) {
  11. for(String key : cacheManagerImpl.getAllKeys()) {
  12. if (cacheManagerImpl.isTimeOut(key)) {
  13. cacheManagerImpl.clearByKey(key);
  14. logger.info(key + "缓存被清除");
  15. }
  16. }
  17. }
  18. }
  19. }.start();
  20. }
  21. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

测试类TestCache.java

  1. public class TestCache {
  2. Logger logger = Logger.getLogger( "cacheLog");
  3. /**
  4. * 测试缓存和缓存失效
  5. */
  6. @Test
  7. public void testCacheManager() {
  8. CacheManagerImpl cacheManagerImpl = new CacheManagerImpl();
  9. cacheManagerImpl.putCache( "test", "test", 10 * 1000L);
  10. cacheManagerImpl.putCache( "myTest", "myTest", 15 * 1000L);
  11. CacheListener cacheListener = new CacheListener(cacheManagerImpl);
  12. cacheListener.startListen();
  13. logger.info( "test:" + cacheManagerImpl.getCacheByKey( "test").getDatas());
  14. logger.info( "myTest:" + cacheManagerImpl.getCacheByKey( "myTest").getDatas());
  15. try {
  16. TimeUnit.SECONDS.sleep( 20);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. logger.info( "test:" + cacheManagerImpl.getCacheByKey( "test"));
  21. logger.info( "myTest:" + cacheManagerImpl.getCacheByKey( "myTest"));
  22. }
  23. /**
  24. * 测试线程安全
  25. */
  26. @Test
  27. public void testThredSafe() {
  28. final String key = "thread";
  29. final CacheManagerImpl cacheManagerImpl = new CacheManagerImpl();
  30. ExecutorService exec = Executors.newCachedThreadPool();
  31. for ( int i = 0; i < 100; i++) {
  32. exec.execute( new Runnable() {
  33. public void run() {
  34. if (!cacheManagerImpl.isContains(key)) {
  35. cacheManagerImpl.putCache(key, 1, 0);
  36. } else {
  37. //因为+1和赋值操作不是原子性的,所以把它用synchronize块包起来
  38. synchronized (cacheManagerImpl) {
  39. int value = (Integer) cacheManagerImpl.getCacheDataByKey(key) + 1;
  40. cacheManagerImpl.putCache(key,value , 0);
  41. }
  42. }
  43. }
  44. });
  45. }
  46. exec.shutdown();
  47. try {
  48. exec.awaitTermination( 1, TimeUnit.DAYS);
  49. } catch (InterruptedException e1) {
  50. e1.printStackTrace();
  51. }
  52. logger.info(cacheManagerImpl.getCacheDataByKey(key).toString());
  53. }
  54. }
  • 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

testCacheManager()输出结果如下:

  1. 2017-4-17 10:33:51 io.github.brightloong.cache.TestCache testCacheManager
  2. 信息: test: test
  3. 2017-4-17 10:33:51 io.github.brightloong.cache.TestCache testCacheManager
  4. 信息: myTest:myTest
  5. 2017-4-17 10:34:01 io.github.brightloong.cache.CacheListener $1 run
  6. 信息: test缓存被清除
  7. 2017-4-17 10:34:06 io.github.brightloong.cache.CacheListener $1 run
  8. 信息: myTest缓存被清除
  9. 2017-4-17 10:34:11 io.github.brightloong.cache.TestCache testCacheManager
  10. 信息: test:null
  11. 2017-4-17 10:34:11 io.github.brightloong.cache.TestCache testCacheManager
  12. 信息: myTest:null
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

testThredSafe()输出结果如下(选出了各种结果中的一个举例):

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

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

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

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

猜你喜欢

转载自blog.csdn.net/u011490072/article/details/80887261