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
.