緩存是在web開發中經常用到的,将程式經常使用到或調用到的對象存在記憶體中,或者是耗時較長但又不具有實時性的查詢資料放入記憶體中,在一定程度上可以提高性能和效率。下面我實作了一個簡單的緩存,步驟如下。
建立緩存對象EntityCache.java
public class EntityCache {
private Object datas;
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
public interface ICacheManager {
void putCache(String key, EntityCache cache);
void putCache(String key, Object datas, long timeOut);
EntityCache getCacheByKey(String key);
Object getCacheDataByKey(String key);
Map getCacheAll();
boolean isContains(String key);
void clearAll();
void clearByKey(String key);
boolean isTimeOut(String key);
Set getAllKeys();
}
實作接口ICacheManager,CacheManagerImpl.java
這裡我使用了ConcurrentHashMap來儲存緩存,本來以為這樣就是線程安全的,其實不然,在後面的測試中會發現它并不是線程安全的。
public class CacheManagerImpl implements ICacheManager {
private static Map caches = new ConcurrentHashMap();
public void putCache(String key, EntityCache cache) {
caches.put(key, cache);
}
public void putCache(String key, Object datas, long timeOut) {
timeOut = timeOut > 0 ? timeOut : 0L;
putCache(key, new EntityCache(datas, timeOut, System.currentTimeMillis()));
}
public EntityCache getCacheByKey(String key) {
if (this.isContains(key)) {
return caches.get(key);
}
return null;
}
public Object getCacheDataByKey(String key) {
if (this.isContains(key)) {
return caches.get(key).getDatas();
}
return null;
}
public Map getCacheAll() {
return caches;
}
public boolean isContains(String key) {
return caches.containsKey(key);
}
public void clearAll() {
caches.clear();
}
public void clearByKey(String key) {
if (this.isContains(key)) {
caches.remove(key);
}
}
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;
}
public Set getAllKeys() {
return caches.keySet();
}
}
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();
}
}
測試類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());
}
}
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
testThredSafe()輸出結果如下(選出了各種結果中的一個舉例):
2017-4-17 10:35:36 io.github.brightloong.cache.TestCache testThredSafe
資訊: 96
可以看到并不是預期的結果100,為什麼呢?ConcurrentHashMap隻能保證單次操作的原子性,但是當複合使用的時候,沒辦法保證複合操作的原子性,以下代碼:
if (!cacheManagerImpl.isContains(key)) {
cacheManagerImpl.putCache(key, 1, 0);
}
多線程的時候回重複更新value,設定為1,是以出現結果不是預期的100。是以辦法就是在CacheManagerImpl.java中都加上synchronized,但是這樣一來相當于操作都是串行,使用ConcurrentHashMap也沒有什麼意義,不過隻是簡單的緩存還是可以的。或者對測試方法中的run裡面加上synchronized塊也行,都是大同小異。更高效的方法我暫時也想不出來,希望大家能多多指教。