很多場(chǎng)景下,有些請(qǐng)求的數(shù)據(jù)渡八,是不會(huì)經(jīng)常改變的啃洋,這種時(shí)候,為了減少數(shù)據(jù)庫(kù)的查詢壓力屎鳍,可以將這一部分?jǐn)?shù)據(jù)放入緩存中宏娄,直接從緩存中讀取。除了一些像Redis等緩存外逮壁,還可以通過本地內(nèi)存绝编,作為緩存。下邊將使用ConcurrentHashMap來實(shí)現(xiàn)本地緩存貌踏。
相關(guān)的技術(shù):
- ConcurrentHashMap --數(shù)據(jù)存儲(chǔ)十饥,線程安全的map
- ScheduledExecutorService --線程定時(shí)調(diào)度服務(wù)
- TimerTask --定時(shí)任務(wù)
- lambda表達(dá)式
整體思路
- 用線程安全的ConcurrentHashMap來作為緩存數(shù)據(jù)的存儲(chǔ),
- 然后通過定時(shí)調(diào)度任務(wù)TimerTask祖乳,來實(shí)現(xiàn)控制緩存的有效時(shí)間逗堵,根據(jù)緩存設(shè)置的超時(shí)時(shí)間,來定時(shí)清除對(duì)應(yīng)的 key眷昆,實(shí)現(xiàn)緩存過期
- 實(shí)現(xiàn)一些靜態(tài)方法蜒秤,來增加緩存汁咏、獲取緩存等
定時(shí)任務(wù)也可也以用Timer來進(jìn)行調(diào)度,但是Timer與ScheduledExecutorService相比有一些缺陷作媚,具體對(duì)比攘滩,可以另行查看。
- 多線程并行處理定時(shí)任務(wù)時(shí)纸泡,Timer運(yùn)行多個(gè)TimeTask時(shí)漂问,只要其中之一沒有捕獲拋出的異常其它任務(wù)便會(huì)自動(dòng)終止運(yùn)行,使用ScheduledExecutorService則沒有這個(gè)問題
- Timer內(nèi)部是一個(gè)線程女揭,任務(wù)1所需的時(shí)間超過了兩個(gè)任務(wù)間的間隔時(shí)會(huì)導(dǎo)致問題
- Timer執(zhí)行周期任務(wù)時(shí)依賴系統(tǒng)時(shí)間
LocalCache整體結(jié)構(gòu)
初始化數(shù)據(jù)
/**
* 默認(rèn)緩存時(shí)長(zhǎng) 單位s
*/
private static final int DEFAULT_TIMEOUT = 3600;
/**
* 默認(rèn)緩存容量
*/
private static final int DEFAULT_SIZE = 1000;
/**
* 存儲(chǔ)數(shù)據(jù)
*/
private static final Map<String,Object> data;
private static final ScheduledExecutorService executorService;
//初始化
static {
data = new ConcurrentHashMap<>(DEFAULT_SIZE);
executorService = new ScheduledThreadPoolExecutor(2);
}
/**
* 私有化構(gòu)造函數(shù)
*/
private LocalCache(){}
刪除緩存的定時(shí)任務(wù)
定時(shí)任務(wù)主要是實(shí)現(xiàn)TimerTask類中的run方法蚤假,傳入對(duì)應(yīng)的key,然后從緩存中移除對(duì)應(yīng)的鍵值對(duì)吧兔,所以實(shí)現(xiàn)方式有三種:靜態(tài)內(nèi)部類磷仰、匿名內(nèi)部類、以及l(fā)ambda方式境蔼,選擇熟悉的一種即可
//靜態(tài)內(nèi)部類
static class CacheCleanTask extends TimerTask {
private String key;
private CacheCleanTask(String key){
this.key = key;
}
public static CacheCleanTask cacheTask(String key){
return new CacheCleanTask(key);
}
@Override
public void run() {
//移除對(duì)應(yīng) key
LocalCache.remove(key);
}
}
增加緩存
/**
* 增加緩存 默認(rèn)有效時(shí)長(zhǎng)
* @param key
* @param value
*/
public static void put(String key, Object value){
data.put(key,value);
//定時(shí)器 調(diào)度任務(wù)灶平,用于根據(jù) 時(shí)間 定時(shí)清除 對(duì)應(yīng)key 緩存
executorService.schedule(new TimerTask() {
@Override
public void run() {
remove(key);
}
}, DEFAULT_TIMEOUT, TimeUnit.SECONDS);
}
/**
* 增加緩存 并設(shè)置緩存時(shí)長(zhǎng) 單位 s
* @param key
* @param value
* @param timeout 緩存時(shí)長(zhǎng) 單位s
*/
public static void put(String key, Object value, int timeout){
data.put(key, value);
//lambda 替換匿名內(nèi)部類
executorService.schedule(() -> remove(key), timeout, TimeUnit.SECONDS);
}
獲取緩存
/**
* 獲取緩存
* @param key
* @return
*/
public static Object get(String key){
return data.get(key);
}
/**
* 獲取當(dāng)前緩存中 所有的key
* @return
*/
public static Set<String> cacheKeys(){
return data.keySet();
}
刪除緩存
/**
* 刪除緩存
* @param key
*/
public static void remove(String key){
data.remove(key);
}
/**
* 清空所有緩存
*/
public static void clear(){
if(size() > 0){
data.clear();
}
}
測(cè)試方法就不貼了,其他更多相關(guān)方法的實(shí)現(xiàn)箍土,可以看一下GitHub上源碼的具體實(shí)現(xiàn)民逼。