martedì 31 gennaio 2012

Put if absent, variante del CheckThenAct

A cura di Flavio Casadei Della Chiesa
Leggi l'articolo completo su flaviocdc.net
  
In questo schema si provvede ed inserire un elemento (di solito) in una collezione solo se questa già non lo contiene.
 
// schema generico di put-if-absent
private HashMap<K,V> hm .....
    public void putIfAbsent(K k , V v) {
        if (!hm.containsKey(k)) {
                hm.put(k,v);
        }
    }
i problemi sono i soliti derivanti dal CheckThenAct. Nota, come in tutte le azioni composte non basta limitarsi ad aggiungere un synchronized per risolvere il problema.
Per eliminare ogni sorta di problema è necessario rendere atomica l'operazione. Per far questo solo un Thread deve essere in grado di controllare la presenza o meno della chiave nella mappa e di effettuare un'eventuale sostituzione.
Un esempio di soluzione può essere il seguente nel quale si istanzia una HashMap all'interno di una classe contenitrice
public class InnerPIA<K,V>  {
 private final HashMap<K,V>  delegate = new HashMap<K,V>();
 public synchronized V get(K k)  {return delegate.get(k);}
 public synchronized void put(K k, V v)  { delegate.put(k,v);}
 public synchronized  boolean containsKey(K k) {return delegate.containsKey(k);}
public synchronized void putIfAbsent(K k, V v)  {
 if (!delegate.containsKey(k) {
     delegate.put(k,v);
 }
}
.....
 }
Nel caso in cui la HashMap sia condivisa e non confinata in una classe contenitrice è necessario utilizzare un altro tipo di ClientSideLocking. In questo modo però tutte le classi che condividono la solita istanza della mappa devono utilizzare il solito protocollo.
public class DelegatePIAPIA<K,V>  {
 private final Map<K,V>  delegate ;
 public DelegatePIA(Map<K,V> m) {
  this.delegate = m;
 }
 public  V get(K k)  {
  synchronized(delegate) {
   return delegate.get(k);
  }
 }
 public  void put(K k, V v)  {
  synchronized(delegate) {
   delegate.put(k,v);
  }
 }
 public   boolean containsKey(K k) {
  synchronized(delegate) {
   return delegate.containsKey(k);
  }
 }
public  void putIfAbsent(K k, V v)  {
  synchronized(delegate) {
   if (!delegate.containsKey(k) {
    delegate.put(k,v);
   }
  }
}
.....
 }
In Java5, precisamente nella concurrent API, esiste un modo più immediato: utilizzare una implemantazione di una ConcurrentMap come ad esempio la classe ConcurrentHashMap.
Queste dispongono di un metodo chiamato appunto putIfAbsent la cui firma è simile alla precedente; l'unica differenza è che il metodo non è void ma ritorna n elemento di tipo V. Invocare il metodo
pippo = mappa.putIfAbsent(key,value);
è equivalente a
 if (!mappa.containsKey(key)) 
      return mappa.put(key, value);
   else
      return mappa.get(key);
Se la mappa conteneva un valore precedente per la chiave key il metodo ritorna tale valore, altrimenti restituisce null.

Nessun commento:

Posta un commento