利用DelayQueue實(shí)現(xiàn)一個(gè)可定時(shí)的緩存,使用單例模式篷角,以便調(diào)用的時(shí)候都是同一個(gè)對(duì)象焊刹,定時(shí)器用DelayQueue來實(shí)現(xiàn),還可以支持多線程情況下的調(diào)用
DelayQueue簡介
DelayQueue是一個(gè)無界的BlockingQueue恳蹲,用于放置實(shí)現(xiàn)了Delayed接口的對(duì)象虐块,其中的對(duì)象只能在其到期時(shí)才能從隊(duì)列中取走。這種隊(duì)列是有序的嘉蕾,即隊(duì)頭對(duì)象的延遲到期時(shí)間最長贺奠。注意:不能將null元素放置到這種隊(duì)列中。
因?yàn)镈elayQueue是基于PriorityQueue實(shí)現(xiàn)的,PriorityQueue底層是一個(gè)堆,可以按時(shí)間排序错忱,所以等待隊(duì)列本身只需要維護(hù)根節(jié)點(diǎn)的一個(gè)定時(shí)器就可以了儡率,而且插入和刪除都是時(shí)間復(fù)雜度都是logn,資源消耗很少航背,作為一個(gè)緩存的定時(shí)裝置是非常適合的
使用 DelayQueue 需要傳入一個(gè)Delayed對(duì)象喉悴,要實(shí)現(xiàn)兩個(gè)方法
1 getDelay(TimeUnit unit) 表示當(dāng)前對(duì)象關(guān)聯(lián)的延時(shí)棱貌,在本例中表示生存時(shí)間
2 compareTo(Delayed o) 堆在進(jìn)行刪除和插入時(shí)需要進(jìn)行對(duì)比玖媚,所以要傳入一個(gè)比較器
實(shí)例展示
public class ExpiryMap {
private static final Logger log = LoggerFactory.getLogger(ExpiryMap.class);
private ExpiryMap() {
}
private static volatile ExpiryMap expiryMap;
/**
* 鍵值對(duì)集合
*/
private final static Map<String, Object> map = new HashMap<>();
/**
* 使用阻塞隊(duì)列中的等待隊(duì)列,因?yàn)镈elayQueue是基于PriorityQueue實(shí)現(xiàn)的婚脱,而PriorityQueue底層是一個(gè)最小堆今魔,可以按過期時(shí)間排序,
* 所以等待隊(duì)列本身只需要維護(hù)根節(jié)點(diǎn)的一個(gè)定時(shí)器就可以了障贸,而且插入和刪除都是時(shí)間復(fù)雜度都是logn错森,資源消耗很少
*/
private final static DelayQueue<DelayData<String>> delay = new DelayQueue<>();
//使用單例模式,加上雙重驗(yàn)證篮洁,可適用于多線程高并發(fā)情況
public static ExpiryMap getInstance() {
if (expiryMap == null) {
synchronized (ExpiryMap.class) {
if (expiryMap == null) {
expiryMap = new ExpiryMap();
//生成一個(gè)線程掃描等待隊(duì)列的值
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.execute(()->{
while (true) {
try {
//此方法是阻塞的涩维,沒有到過期時(shí)間就阻塞在這里,直到取到數(shù)據(jù)
DelayData<String> take = delay.take();
map.remove(take.data);
} catch (InterruptedException e) {
log.error("ExpiryMap線程被打斷");
}
}
});
}
}
}
return expiryMap;
}
/**
* 添加緩存
*
* @param key 鍵
* @param data 值
* @param expire 過期時(shí)間袁波,單位:毫秒瓦阐, 0表示無限長
*/
public void put(String key, Object data, long expire) {
map.put(key,data);
//當(dāng)?shù)扔?時(shí),就不把過期時(shí)間放進(jìn)隊(duì)列里了篷牌,值在代碼運(yùn)行期間會(huì)一直存在
if (expire != 0) {
delay.offer(new DelayData<>(key, System.currentTimeMillis() + expire));
}
}
/**
* 讀取緩存
*
* @param key 鍵
* @return 值
*/
public Object get(String key) {
return map.get(key);
}
/**
* 清除緩存
*
* @param key 鍵
* @return 值
*/
public Object remove(String key) {
return map.remove(key);
}
/**
* 查詢當(dāng)前緩存的鍵值對(duì)數(shù)量
* @return 數(shù)量
*/
public int size() {
return map.size();
}
//試下Delayed接口
static class DelayData<T> implements Delayed{
private T data;
//到期時(shí)間
private long expire;
public DelayData(T data, long expire) {
this.data = data;
this.expire = expire;
}
//如果返回小于0就代表過期了
@Override
public long getDelay(TimeUnit unit) {
//expire是過期時(shí)的時(shí)間
long diffTime= expire- System.currentTimeMillis();
return unit.convert(diffTime,TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return (int)(this.expire - ((DelayData<T>) o).getExpire());
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public long getExpire() {
return expire;
}
public void setExpire(long expire) {
this.expire = expire;
}
}
寫一個(gè)測(cè)試方法
分別像緩存中放入3個(gè)值睡蟋,時(shí)間分別是1s,2s枷颊,3s戳杀,每過1s该面,打印下結(jié)果
public static void main(String[] args) throws InterruptedException {
ExpiryMap instance = getInstance();
instance.put("key1","key1",1000);
instance.put("key2","key2",2000);
instance.put("key3","key3",3000);
System.out.println(instance.get("key1"));
System.out.println(instance.get("key2"));
System.out.println(instance.get("key3"));
Thread.sleep(1000);
System.out.println("-----------------");
System.out.println(instance.get("key1"));
System.out.println(instance.get("key2"));
System.out.println(instance.get("key3"));
Thread.sleep(1000);
System.out.println("-----------------");
System.out.println(instance.get("key1"));
System.out.println(instance.get("key2"));
System.out.println(instance.get("key3"));
Thread.sleep(1000);
System.out.println("-----------------");
System.out.println(instance.get("key1"));
System.out.println(instance.get("key2"));
System.out.println(instance.get("key3"));
}
輸出結(jié)果
可以看出,緩存每過一秒就會(huì)被刪除一個(gè)元素信卡,刪除時(shí)間是根據(jù)傳入的過期時(shí)間清除的
key1
key2
key3
-----------------
null
key2
key3
-----------------
null
null
key3
-----------------
null
null
null