Не так давно, мне выпала задача, написать кеш, который сам себя чистит по истечению некоторого определенного времени. Требования к нему были следующие:
В общем-то и все. До написания данной задачи с java.util.concurrent дела не имел. На мысль использования этого пакета меня натолкнул один мой коллега, у которого было нечто подобное, но не соответствовало тому функционалу который был нужен. Итак, начнем:
В качестве ключа будет выступать внутренний класс, который помимо прямого назначения будет определять он является «живым» или его можно удалить с кеша, так как время его существования подошло к концу:
Сам класс кеша сделаем параметризированным. Внутри нам потребуется контейнер-хранилище. java.util.concurrent.ConcurrentHashMap лучше всего подходит. Время хранения по-умолчанию сдалаем отдельным полем. Далее создадим java.util.concurrent.ScheduledExecutorService:
Поток сделаем демоном, для того чтоб при завершении основного потока процесс, который чистит кеш так же завершался.
В конструкторе запустим наш шедулер, который через определенное время (в нашем случае это пятая часть времени хранения объекта.) будет пробегаться по карте и удалять все те объекты время жизни которых — истекло:
Далее следует добавить методы для работы с кешом — и все готово к использованию. Вот полный код:
- Легковесность
- Потокобезобасность
В общем-то и все. До написания данной задачи с 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 Key(Object key) { this.key = key; } 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 Cache(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)); } /** * удаляет все значения по ключу из кеша * @param <K> * @param key - ключ */ public void remove(K key) { globalMap.remove(new Key(key)); } /** * Удаляет все значения из кеша */ 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 Карта с данными */ public void addAll(Map<K, V> map) { for (Entry<K, V> entry : map.entrySet()) { put(entry.getKey(), 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 Key(Object key) { this.key = key; } 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 + '}'; } } }
