2010-01-15 3 views
15

Я хотел бы реализовать простое кэширование тяжелых объектов в веб-приложении Java. Но я не могу понять, как это сделать должным образом.Реализация кэша с использованием java ConcurrentHashMap

Недостаточно ли чего-то или методов ConcurrentHashMap (putIfAbsent, ...) недостаточно, и необходима дополнительная синхронизация?

Есть ли лучший простой API (для хранения данных, без внешней конфигурации) для этого?

П.

+1

Просто интересно: каковы ваши требования к кешированию? Вам нужно кэшировать полное транзитивное закрытие вашего объекта с тяжелым весом, чтобы он был согласован в кластере серверов приложений? Если это так, это нетривиальная проблема для решения, и вам может быть лучше использовать библиотеку кеша, такую ​​как ehcache. – Alan

ответ

14

Если это безопасно, чтобы временно иметь более одного экземпляра для вещи, которую вы пытаетесь кэш, вы можете сделать «безблокировочного» кэш, как это:

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; 
} 

несколько потоков может «расы» создавать и добавлять элемент для ключа, но только один должен «выиграть».

+0

Что делать, если вы хотите иметь метод обновления, который заменяет/обновляет тяжелый объект для заданного ключа? – Paolo1976

+0

Или просто используйте MapMaker, и только один поток когда-либо создаст Heavy. Если другой поток нуждается в нем, пока он все еще находится в середине его создания, он просто ждет результата. –

+0

@Paolo: Я позволю гуру-гуру «MapMaker» ответить на это. – Ken

0

ConcurrentHashMap должно быть достаточно для ваших нужд putIfAbsent является поточно.

Не уверен, насколько проще вы можете получить

ConcurrentMap myCache = new ConcurrentHashMap(); 

Пол

2

Вместо того, чтобы в «тяжелые предметы» в кэш, вы можете использовать свет фабричные объекты для создания активного кэша.

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; 
25

В дополнение к ответу Кена, если создать тяжелый объект, который позже будет выброшен, НЕ приемлемо (вы хотите гарантировать, что по какой-либо причине создается только один объект для каждой клавиши), тогда вы можете сделать это. .. фактически, не делаем. Не делай этого сам. Используйте google-collections (теперь guava) MapMaker class:

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) просто работает и полностью удаляет вас от необходимости беспокоиться о сложных аспектах параллельности и syncrhonization.

Обратите внимание, что если вы хотите, чтобы добавить некоторые причудливые особенности, как и истечение срока действия, это просто

Map<....> cache = new MapMaker<....>() 
    .expiration(30, TimeUnit.MINUTES) 
    .makeComputingMap(.....) 

и вы также можете легко использовать мягкие или слабые значения либо ключи или данные в случае необходимости (см Javadoc более подробности)

+0

Ничего себе, какое красивое и элегантное решение! – Benjamin

0

Я понимаю, что это старое сообщение, но в 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()); 
    } 
} 
Смежные вопросы