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.

È stato utile?

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());
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top