2010-01-15 22 views
15

Vorrei implementare una semplice memorizzazione nella cache di oggetti pesanti in un'applicazione web java. Ma non riesco a capire come farlo correttamente.Implementazione di una cache utilizzando una Java ConcurrentHashMap

Mi manca qualcosa o i metodi ConcurrentHashMap (putIfAbsent, ...) non sono sufficienti e è necessaria ulteriore sincronizzazione?

Esiste un'API migliore (nella memoria, nessuna configurazione esterna) per eseguire questa operazione?

P.

+1

Basta chiedersi: quali sono i requisiti per il caching? È necessario memorizzare nella cache la chiusura transitiva completa dell'oggetto pesante in modo che sia coerente nel cluster dei server delle applicazioni? Se è così, questo è un problema non banale da risolvere, e potrebbe essere meglio usare una libreria cache come ehcache. – Alan

risposta

14

Se è sicuro di avere temporaneamente più di un'istanza per la cosa si sta cercando di cache, si può fare una cache "libero-lock" come questo:

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

Più thread può "gareggiare" per creare e aggiungere un oggetto per la chiave, ma solo uno dovrebbe "vincere".

+0

Cosa fare se si desidera disporre di un metodo di aggiornamento che sostituisca/aggiorni l'oggetto pesante per una determinata chiave? – Paolo1976

+0

Oppure basta usare MapMaker e solo un thread creerà il pesante. Se un altro thread ne ha bisogno mentre è ancora in fase di creazione, attenderà semplicemente il risultato. –

+0

@Paolo: lascio che siano i guru di MapMaker che votano per la votazione. – Ken

0

ConcurrentHashMap dovrebbe essere sufficiente per le vostre esigenze putIfAbsent è thread-safe.

Non sono sicuro quanto più semplice è possibile ottenere

ConcurrentMap myCache = new ConcurrentHashMap(); 

Paul

2

Invece di mettere gli "oggetti pesanti" nella cache, è possibile utilizzare gli oggetti di luce fabbrica per creare una cache attiva.

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

Oltre alla risposta di Ken, se la creazione di un oggetto pesante che successivamente viene gettato via NON è accettabile (si desidera garantire che solo un oggetto venga creato per ogni chiave, per qualche motivo), è possibile farlo tramite .. .. in realtà, no. Non farlo da solo Utilizzare il google-collections (ora 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 
     } 
    }); 

Poi un semplice cache.get(key) solo funziona e completamente si rimuove dal doversi preoccupare di aspetti difficili della concorrenza e syncrhonization.

Si noti che se si desidera aggiungere alcune caratteristiche più elaborate, come la scadenza, è solo

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

e si può anche usare facilmente valori soft o deboli sia per le chiavi o dati se necessario (consultare Javadoc per più dettagli)

+0

Wow, che soluzione carina ed elegante! – Benjamin

0

Mi rendo conto che questo è un vecchio post, ma in java 8 questo può essere fatto senza creare un oggetto pesante potenzialmente inutilizzato con una 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()); 
    } 
} 
Problemi correlati