L'implementazione di una cache utilizzando un ConcurrentHashMap Java
-
20-09-2019 - |
Domanda
mi piacerebbe implementare un semplice caching di oggetti pesanti in un'applicazione Web Java. Ma io non riesco a capire come farlo correttamente.
Mi sto perdendo qualcosa o ConcurrentHashMap metodi (putIfAbsent, ...) non sono sufficienti e non è necessario eseguire un ulteriore?
C'è una migliore API semplice (In memoria di archiviazione, senza configurazione esterna) per fare questo?
P.
Soluzione
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 possono "corsa" per creare e aggiungere una voce per la chiave, ma solo uno dovrebbe "vincere".
Altri suggerimenti
A seguito della risposta di Ken, se la creazione di un oggetto pesante che poi si butta via non è accettabile (si vuole garantire che solo un oggetto viene creato per ogni tasto, per qualche ragione), allora si può fare questo ... . in realtà, non lo fanno. Non farlo da soli. Utilizzare il google-collezioni (ora guava ) class MapMaker :
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 lavori e completamente si rimuove da 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 utilizzare facilmente i valori morbidi o deboli sia per le chiavi o dati se necessario (consultare Javadoc per maggiori dettagli)
Invece di mettere gli "oggetti pesanti" nella cache, è possibile utilizzare gli oggetti fabbrica di luce 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;
ConcurrentHashMap dovrebbe essere sufficiente per le vostre esigenze putIfAbsent è thread-safe.
Non sono sicuro quanto più semplice è possibile ottenere
ConcurrentMap myCache = new ConcurrentHashMap();
Paul
Mi rendo conto che questo è un vecchio post, ma in Java 8 questo può essere fatto senza creare un oggetto pesante potenzialmente non utilizzato 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());
}
}