天天看點

java 緩存_Java實作一個簡單的緩存方法

緩存是在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塊也行,都是大同小異。更高效的方法我暫時也想不出來,希望大家能多多指教。