Реализация кэша с использованием Java ConcurrentHashMap
Я хотел бы реализовать простое кэширование тяжеловесных объектов в веб-приложении java. Но я не могу понять, как это сделать правильно.
Я что-то пропустил или методы ConcurrentHashMap (putIfAbsent,...) недостаточно и необходима дополнительная синхронизация ?
есть ли лучший простой API (в памяти, без внешней конфигурации) для этого ?
П.
5 ответов
Если безопасно временно иметь более одного экземпляра для вещи, которую вы пытаетесь кэшировать, вы можете сделать кэш "без блокировки" следующим образом:
public Heavy instance(Object key) {
Heavy info = infoMap.get(key);
if ( info == null ) {
// It's OK to construct a Heavy that ends up not being used
info = new Heavy(key);
Heavy putByOtherThreadJustNow = infoMap.putIfAbsent(key, info);
if ( putByOtherThreadJustNow != null ) {
// Some other thread "won"
info = putByOtherThreadJustNow;
}
else {
// This thread was the winner
}
}
return info;
}
несколько потоков могут "соревноваться", чтобы создать и добавить элемент для ключа, но только один должен"выиграть".
далее к ответу Кена, если создание тяжеловесного объекта, который позже выбрасывается, неприемлемо (вы хотите гарантировать, что по какой-то причине для каждого ключа создается только один объект), то вы можете это сделать.... вообще-то, нет. Не делай этого сам. Используйте google-коллекции (ныне гуавы) картографа класс:
Map<KeyType, HeavyData> cache = new MapMaker<KeyType, HeavyData>()
.makeComputingMap(new Function<KeyType, HeavyData>() {
public HeavyData apply(KeyType key) {
return new HeavyData(key); // Guaranteed to be called ONCE for each key
}
});
и cache.get(key)
просто работает и полностью удаляет вас от необходимости переживать сложные аспекты параллелизма и синхронизации.
обратите внимание, что если вы хотите добавить некоторые более модные функции, такие как истечение срока действия, это просто
Map<....> cache = new MapMaker<....>()
.expiration(30, TimeUnit.MINUTES)
.makeComputingMap(.....)
и вы также можете легко использовать мягкие или слабые значения для ключей или данных, если это необходимо (см. Javadoc для более подробной информации)
вместо того, чтобы помещать "тяжелые объекты" в кэш, вы можете использовать объекты light factory для создания активного кэша.
public abstract class LazyFactory implements Serializable {
private Object _heavyObject;
public getObject() {
if (_heavyObject != null) return _heavyObject;
synchronized {
if (_heavyObject == null) _heavyObject = create();
}
return _heavyObject;
}
protected synchronized abstract Object create();
}
// here's some sample code
// create the factory, ignore negligible overhead for object creation
LazyFactory factory = new LazyFactory() {
protected Object create() {
// do heavy init here
return new DbConnection();
};
};
LazyFactory prev = map.pufIfAbsent("db", factory);
// use previous factory if available
return prev != null ? prev.getObject() : factory.getObject;
ConcurrentHashMap должно быть достаточно для ваших потребностей putIfAbsent является потокобезопасным.
Не уверен, насколько проще вы можете получить
ConcurrentMap myCache = new ConcurrentHashMap();
Павел
Я понимаю, что это старый пост, но в java 8 это можно сделать без создания потенциально неиспользуемого тяжелого объекта с ConcurrentHashMap.
public class ConcurrentCache4<K,V> {
public static class HeavyObject
{
}
private ConcurrentHashMap<String, HeavyObject> cache = new ConcurrentHashMap<>();
public HeavyObject get(String key)
{
HeavyObject heavyObject = cache.get(key);
if (heavyObject != null) {
return heavyObject;
}
return cache.computeIfAbsent(key, k -> new HeavyObject());
}
}