所有item的TTL都一样,比如所有的http session的过期时间默认30分钟,idle超过30分钟就失效。相对的是每个item指定TTL的缓存。
实现和LRU类似,都是map + list,不同的是触发机制不是容量满,而是idle时间。专门一个线程check,重点是,不是O(n)的遍历检查,而是利用队列维护先后顺序,队头是最老的,如果队头都没过期,其他的都不可能过期,而且check线程会wait这个时间差再次check,而不是以固定的间隔无谓的check
class DeList<T> {
public static class Node<T> {
public T data;
public Node<T> prev;
public Node<T> next;
public Node(T data) {
this.data = data;
}
public Node(T data, Node<T> prev, Node<T> next) {
this.data = data;
this.prev = prev;
this.next = next;
}
}
Node<T> head = null, tail = null;
public void addLast(Node<T> node) {
node.prev = null;
node.next = null;
if (tail == null) {
head = node;
} else {
tail.next = node;
node.prev = tail;
}
tail = node;
}
public void remove(Node<T> node) {
if (node.prev != null) node.prev.next = node.next;
if (node.next != null) node.next.prev = node.prev;
if (node == head) head = node.next;
if (node == tail) tail = node.prev;
}
public T peekFirst() {
return head == null ? null : head.data;
}
public T pollFirst() {
if (head == null) return null;
Node<T> node = head;
head = head.next;
if (head != null) head.prev = null;
else tail = null;
return node.data;
}
}
public class FixedTTLCache<K, V> implements Iterable<FixedTTLCache<K, V>.Entry>{
public class Entry{
K key;
V value;
long dueTime; //millisecond from 1970
public Entry(K key, V value, long dueTime) {
this.key = key;
this.value = value;
this.dueTime = dueTime;
}
}
private Map<K, DeList.Node<Entry>> locator = new HashMap<K, DeList.Node<Entry>>();
private DeList<Entry> list = new DeList<Entry>();
private long TTL;
private Lock lock = new ReentrantLock();
private Condition available = lock.newCondition();
private Thread expireThread;
private volatile boolean exit = false;
public FixedTTLCache(long ttl) {
this.TTL = ttl;
expireThread = new Thread(new Runnable() {
public void run() {
expireCheck();
}
}
);
expireThread.start();
}
public void put(K key, V value) {
try {
lock.lock();
DeList.Node<Entry> node = locator.get(key);
if (node != null) {
node.data.value = value;
list.remove(node);
} else {
node = new DeList.Node<Entry>(new Entry(key, value, -1));
locator.put(key, node);
}
list.addLast(node);
node.data.dueTime = System.currentTimeMillis() + TTL;
if (locator.size() == 1) this.available.signalAll();
} finally {
lock.unlock();
}
}
public V get(K key) {
try {
lock.lock();
DeList.Node<Entry> node = locator.get(key);
if (node == null) return null;
list.remove(node);
list.addLast(node);
node.data.dueTime = System.currentTimeMillis() + TTL;
return node.data.value;
}
finally {
lock.unlock();
}
}
public int size() {return this.locator.size();}
@Override
public Iterator<Entry> iterator() {
return new Iterator<Entry>() {
DeList.Node<Entry> cur = list.head;
@Override
public boolean hasNext() {
return cur != null;
}
@Override
public Entry next() {
Entry ret = cur.data;
cur = cur.next;
return ret;
}
};
}
private void expireCheck() {
while (!exit) {
try {
lock.lock();
Entry e = list.peekFirst();
if (e == null) {
System.out.println("Checker: empty, waitting...");
available.await();
}
else {
if (e.dueTime > System.currentTimeMillis()) {
available.await(e.dueTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
} else {
e = list.pollFirst();
System.out.println("expire key " + e.key);
locator.remove(e.key);
}
}
} catch (InterruptedException e) {
} finally {
lock.unlock();
}
}
System.out.println("Checker exits.");
}
public void stop() {
this.exit = true;
try {
lock.lock();
available.signalAll();
} finally {
lock.unlock();
}
}
}