private static ScheduledExecutorService swapExpiredPool
= new ScheduledThreadPoolExecutor(10);
private ReentrantLock lock = new ReentrantLock();
private ConcurrentHashMap<String, Node> cache = new ConcurrentHashMap<>(1024);
/**
* 讓過期時間最小的數據排在隊列前,在清除過期數據時
* ,只需查看緩存最近的過期數據,而不用掃描全部緩存
*
* @see Node#compareTo(Node)
* @see SwapExpiredNodeWork#run()
*/
private PriorityQueue<Node> expireQueue = new PriorityQueue<>(1024);
public LocalCache() {
//使用默認的線程池,每5秒清除一次過期數據
//線程池和調用頻率 最好是交給調用者去設置。
swapExpiredPool.scheduleWithFixedDelay(
new SwapExpiredNodeWork(), 5, 5, TimeUnit.SECONDS);
}
public Object set(String key, Object value, long ttl) {
Assert.isTrue(StringUtils.hasLength(key), "key can't be empty");
Assert.isTrue(ttl > 0, "ttl must greater than 0");
long expireTime = System.currentTimeMillis() + ttl;
Node newNode = new Node(key, value, expireTime);
lock.lock();
try {
Node old = cache.put(key, newNode);
expireQueue.add(newNode);
//如果該key存在數據,還要從過期時間隊列刪除
if (old != null) {
expireQueue.remove(old);
return old.value;
}
return null;
} finally {
lock.unlock();
}
}
/**
* 拿到的數據可能是已經過期的數據,可以再次判斷一下
* if(n.expireTime<System.currentTimeMillis()){
* return null;
* }
* 也可以直接返回整個節點Node ,交給調用者去取舍
* <p>
* <p>
* 無法判斷不存在該key,還是該key存的是一個null值,如果需要區分這兩種情況
* 可以定義一個全局標識,標識key不存在
* public static final NOT_EXIST = new Object();
* 返回值時
* return n==null?NOT_EXIST:n.value;
*/
public Object get(String key) {
Node n = cache.get(key);
return n == null ? null : n.value;
}
/**
* 刪出KEY,並返回該key對應的數據
*/
public Object remove(String key) {
lock.lock();
try {
Node n = cache.remove(key);
if (n == null) {
return null;
} else {
expireQueue.remove(n);
return n.value;
}
} finally {
lock.unlock();
}
}
/**
* 刪除已經過期的數據
*/
private class SwapExpiredNodeWork implements Runnable {
@Override
public void run() {
long now = System.currentTimeMillis();
while (true) {
lock.lock();
try {
Node node = expireQueue.peek();
//沒有數據了,或者數據都是沒有過期的了
if (node == null || node.expireTime > now) {
return;
}
cache.remove(node.key);
expireQueue.poll();
} finally {
lock.unlock();
}
}
}
}
private static class Node implements Comparable<Node> {
private String key;
private Object value;
private long expireTime;
public Node(String key, Object value, long expireTime) {
this.value = value;
this.key = key;
this.expireTime = expireTime;
}
/**
* @see SwapExpiredNodeWork
*/
@Override
public int compareTo(Node o) {
long r = this.expireTime - o.expireTime;
if (r > 0) {
return 1;
}
if (r < 0) {
return -1;
}
return 0;
}
}