2010-01-15 8 views
15

Ich möchte ein einfaches Caching von schwergewichtigen Objekten in einer Web-Java-Anwendung implementieren. Aber ich kann nicht herausfinden, wie man es richtig macht.Implementieren eines Cache mit einem Java-ConcurrentHashMap

Fehle ich etwas oder ConcurrentHashMap Methoden (PutIfAbsent, ...) sind nicht genug und zusätzliche Synchronisation benötigt wird?

Gibt es eine bessere einfache API (im Speicher, keine externe Konfiguration), um dies zu tun?

P.

+1

Ich frage mich nur: Was sind wirklich Ihre Anforderungen für das Caching? Müssen Sie die vollständige transitive Sperrung Ihres Schwergewichtsobjekts zwischenspeichern, damit es im gesamten Cluster Ihrer Anwendungsserver konsistent ist? Wenn dies der Fall ist, ist dies ein nicht-triviales Problem, das zu lösen ist, und Sie sollten besser eine Cache-Bibliothek wie ehcache verwenden. – Alan

Antwort

14

Wenn es sicher ist, vorübergehend mehr als eine Instanz haben für das, was Sie Cache sind versuchen, können Sie eine „Lock-frei“ Cache wie folgt tun:

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

Mehrere Threads kann "rasen", um einen Gegenstand für den Schlüssel zu erstellen und hinzuzufügen, aber nur einer sollte "gewinnen".

+0

Was ist, wenn Sie eine Update-Methode haben möchten, die das schwere Objekt für einen bestimmten Schlüssel ersetzt/aktualisiert? – Paolo1976

+0

Oder benutze einfach MapMaker und nur ein Thread wird jemals den Heavy erstellen. Wenn ein anderer Thread es benötigt, während es noch in der Mitte ist, wird es einfach auf das Ergebnis warten. –

+0

@Paolo: Ich werde die "MapMaker" -Gurus ablehnen lassen. – Ken

0

ConcurrentHashMap sollte für Ihre Bedürfnisse ausreichend sein putIfAbsent sicher ist fädeln.

nicht sicher, wie viel einfacher Sie

ConcurrentMap myCache = new ConcurrentHashMap(); 

Paul

2

Statt setzen die „schwere Gegenstände“ in den Cache zu bekommen, können Sie Licht Factory-Objekte verwenden, um eine aktive Cache zu erstellen.

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

Weiter zu Kens Antwort, wenn das Erstellen eines schwergewichtigen Objekts, das später weggeworfen wird, NICHT akzeptabel ist (Sie wollen garantieren, dass nur ein Objekt für jeden Schlüssel aus irgendeinem Grund erstellt wird), dann können Sie dies tun, indem .. .. eigentlich nicht. Mach es nicht selbst. Verwenden Sie die google-collections (jetzt 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 
     } 
    }); 

Dann ein einfaches cache.get(key) nur arbeitet und vollständig entfernt man mit etwa tricky Aspekte der Parallelität und syncrhonization kümmern.

Beachten Sie, dass, wenn Sie einige ausgefallenere Funktionen hinzufügen möchten, wie Ablauf, es ist nur

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

und man kann leicht auch weiche oder schwache Werte entweder für Schlüssel oder Daten verwenden, wenn die Javadoc für mehr erforderlich (siehe Details)

+0

Wow, was für eine schöne und elegante Lösung! – Benjamin

0

Ich weiß, dies ist ein alter Beitrag, aber in Java 8 kann dies getan werden, ohne ein potenziell unbenutztes schweres Objekt mit einer ConcurrentHashMap zu erstellen.

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