=== 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 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 {
private final HashMap delegate = new HashMap();
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 {
private final Map delegate ;
public DelegatePIA(Map 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 [[http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ConcurrentMap.html| ConcurrentMap]] come ad esempio la classe [[http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ConcurrentHashMap.html|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''.