2010-01-15 6 views
15

Me gustaría implementar un simple caché de objetos pesados ​​en una aplicación web java. Pero no puedo entender cómo hacerlo correctamente.Implementación de un caché usando un java ConcurrentHashMap

¿Me falta algo o los métodos ConcurrentHashMap (putIfAbsent, ...) no son suficientes y se necesita sincronización adicional?

¿Existe una mejor API simple (en almacenamiento de memoria, sin configuración externa) para hacer esto?

P.

+1

Solo me pregunto: ¿cuáles son realmente sus requisitos para el almacenamiento en caché? ¿Necesita almacenar en caché el cierre transitivo completo de su objeto pesado para que sea uniforme en todo el clúster de los servidores de su aplicación? Si es así, este es un problema no trivial que resolver, y es mejor que uses una biblioteca de caché como ehcache. – Alan

Respuesta

14

si es seguro tener temporalmente más de una instancia de lo que estamos tratando de caché, se puede hacer una caché "sin bloqueo" de esta manera:

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

múltiples hilos puede "competir" para crear y agregar un elemento para la llave, pero solo uno debe "ganar".

+0

¿Qué sucede si desea tener un método de actualización que reemplace/actualice el objeto pesado para una clave determinada? – Paolo1976

+0

O simplemente use MapMaker, y solo un hilo creará Heavy. Si otro hilo lo necesita mientras todavía está en el medio de crearlo, simplemente esperará el resultado. –

+0

@Paolo: Permitiré que los gurús de 'MapMaker' voten hacia abajo. – Ken

0

ConcurrentHashMap debería ser suficiente para sus necesidades putIfAbsent es hilo de seguridad.

No sé cuánto más simple se puede obtener

ConcurrentMap myCache = new ConcurrentHashMap(); 

Paul

2

En lugar de poner los "objetos pesados" en la memoria caché, puede utilizar objetos de fábrica de la luz para crear una caché activa.

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

Además de la respuesta de Ken, si la creación de un objeto pesado que luego se desecha NO es aceptable (desea garantizar que solo se crea un objeto para cada tecla, por algún motivo), puede hacerlo ... .. en realidad, no. No lo hagas tú mismo. Utilice la google-collections (ahora 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 
     } 
    }); 

A continuación, un simple cache.get(key) solo funciona y completamente se quita de tener que preocuparse acerca de los aspectos difíciles de concurrencia y syncrhonization.

Tenga en cuenta que si quieres añadir algunas características más elegantes, como caducidad, es sólo

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

y también se puede utilizar fácilmente los valores blandos o débiles, ya sea para las llaves o los datos si es necesario (consulte el Javadoc para obtener más detalles)

+0

Guau, ¡qué solución tan agradable y elegante! – Benjamin

0

Me doy cuenta de que esta es una publicación anterior, pero en Java 8 esto se puede hacer sin crear un objeto pesado potencialmente no utilizado con un 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()); 
    } 
} 
Cuestiones relacionadas