Не так давно, мне выпала задача, написать кеш, который сам себя чистит по истечению некоторого определенного времени. Требования к нему были следующие:
- Легковесность
- Потокобезобасность
В общем-то и все. До написания данной задачи с java.util.concurrent дела не имел. На мысль использования этого пакета меня натолкнул один мой коллега, у которого было нечто подобное, но не соответствовало тому функционалу который был нужен. Итак, начнем:
В качестве ключа будет выступать внутренний класс, который помимо прямого назначения будет определять он является «живым» или его можно удалить с кеша, так как время его существования подошло к концу:
private static class Key {
private final Object key;
private final long timelife;
public Key(Object key, long timeout) {
this.key = key;
this.timelife = System.currentTimeMillis() + timeout;
}
public Object getKey() {
return key;
}
public boolean isLive(long currentTimeMillis) {
return currentTimeMillis < timelife;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Key other = (Key) obj;
if (this.key != other.key && (this.key == null || !this.key.equals(other.key))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 43 * hash + (this.key != null ? this.key.hashCode() : 0);
return hash;
}
@Override
public String toString() {
return "Key{" + "key=" + key + '}';
}
}
Сам класс кеша сделаем параметризированным. Внутри нам потребуется контейнер-хранилище. java.util.concurrent.ConcurrentHashMap лучше всего подходит. Время хранения по-умолчанию сдалаем отдельным полем. Далее создадим java.util.concurrent.ScheduledExecutorService:
public class CacheUtil<K, V> {
private ConcurrentHashMap<Key, V> globalMap = new ConcurrentHashMap<Key, V>();
private long default_timeout;
private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread th = new Thread(r);
th.setDaemon(true);
return th;
}
});
}
Поток сделаем демоном, для того чтоб при завершении основного потока процесс, который чистит кеш так же завершался.
В конструкторе запустим наш шедулер, который через определенное время (в нашем случае это пятая часть времени хранения объекта.) будет пробегаться по карте и удалять все те объекты время жизни которых — истекло:
public CacheUtil(long default_timeout) throws Exception {
if (default_timeout < 100) {
throw new Exception("Too short interval for storage in the cache. Interval should be more than 10 ms");
}
default_timeout = default_timeout;
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
long current = System.currentTimeMillis();
for (Key k : globalMap.keySet()) {
if (!k.isLive(current)) {
globalMap.remove(k);
}
}
}
}, 1, default_timeout/5, TimeUnit.MILLISECONDS);
}
Далее следует добавить методы для работы с кешом — и все готово к использованию. Вот полный код:
public class Caсhe<K, V> {
private volatile ConcurrentHashMap<Key, V> globalMap = new ConcurrentHashMap<Key, V>();
private long default_timeout;
private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread th = new Thread(r);
th.setDaemon(true);
return th;
}
});
/**
* @param default_timeout количество милисекунд - время которое обьект будет кранится в кеше.
*/
public Cashe(long default_timeout) throws Exception {
if (default_timeout < 10) {
throw new Exception("Too short interval for storage in the cache. Interval should be more than 10 ms");
}
this.default_timeout = default_timeout;
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
long current = System.currentTimeMillis();
for (Key k : globalMap.keySet()) {
if (!k.isLive(current)) {
globalMap.remove(k);
}
}
}
}, 1, default_timeout/5, TimeUnit.MILLISECONDS);
}
/**
* @param default_timeout количество милисекунд - время которое обьект будет кранится в кеше
*/
public void setDefault_timeout(long default_timeout) throws Exception {
if (default_timeout < 100) {
throw new Exception("Too short interval for storage in the cache. Interval should be more than 10 ms");
}
this.default_timeout = default_timeout;
}
/**
* Метод для вставки обьекта в кеш
* Время зранения берётся по умолчанию
* @param <K>
* @param <V>
* @param key ключ в кеше
* @param data данные
*/
public void put(K key, V data) {
globalMap.put(new Key(key, default_timeout), data);
}
/**
* Метод для вставки обьекта в кеш
* @param <K>
* @param <V>
* @param key ключ в кеше
* @param data данные
* @param timeout время хранения обьекта в кеше в милисекундах
*/
public void put(K key, V data, long timeout) {
globalMap.put(new Key(key, timeout), data);
}
/**
* получение значения по ключу
* @param <K>
* @param <V>
* @param key ключ для поиска с кеша
* @return Обьект данных храняшийся в кеше
*/
public V get(K key) {
return globalMap.get(new Key(key, default_timeout));
}
/**
* удаляет все значения по ключу из кеша
* @param <K>
* @param key - ключ
*/
public void remove(K key) {
globalMap.remove(new Key(key, default_timeout));
}
/**
* Удаляет все значения из кеша
*/
public void removeAll() {
globalMap.clear();
}
/**
* Полностью заменяет весь существующий кеш.
* Время хранения по умолчанию.
* @param <K>
* @param <V>
* @param map Карта с данными
*/
public void setAll(Map<K, V> map) {
ConcurrentHashMap tempmap = new ConcurrentHashMap<Key, V>();
for (Entry<K, V> entry : map.entrySet()) {
tempmap.put(new Key(entry.getKey(), default_timeout), entry.getValue());
}
globalMap = tempmap;
}
/**
* Полностью заменяет весь существующий кеш
* с заданым временем хранения
* @param <K>
* @param <V>
* @param map Карта с данными
* @param timeout
*/
public void setAll(Map<K, V> map, long timeout) {
ConcurrentHashMap<Key, V> tempmap = new ConcurrentHashMap<Key, V>();
for (Entry<K, V> entry : map.entrySet()) {
tempmap.put(new Key(entry.getKey(), timeout), entry.getValue());
}
globalMap = tempmap;
}
/**
* Добавляет к сущесвуещему кешу переданую карту
* Время хранения по умолчанию.
* @param <K>
* @param <V>
* @param map Карта с данными
*/
public void addAll(Map<K, V> map) {
for (Entry<K, V> entry : map.entrySet()) {
globalMap.put(new Key(entry.getKey(), default_timeout), entry.getValue());
}
}
/**
* Добавляет к сущесвуещему кешу переданую карту
* с заданым временем хранения
* @param <K>
* @param <V>
* @param map Карта с данными
* @param timeout
*/
public void addAll(Map<K, V> map, long timeout) {
for (Entry<K, V> entry : map.entrySet()) {
globalMap.put(new Key(entry.getKey(), timeout), entry.getValue());
}
}
private static class Key {
private final Object key;
private final long timelife;
public Key(Object key, long timeout) {
this.key = key;
this.timelife = System.currentTimeMillis() + timeout;
}
public Object getKey() {
return key;
}
public boolean isLive(long currentTimeMillis) {
return currentTimeMillis < timelife;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Key other = (Key) obj;
if (this.key != other.key && (this.key == null || !this.key.equals(other.key))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 43 * hash + (this.key != null ? this.key.hashCode() : 0);
return hash;
}
@Override
public String toString() {
return "Key{" + "key=" + key + '}';
}
}
}
Автор: Odis