Put if absent, variante del CheckThenAct

A cura di Flavio Casadei Della Chiesa

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.