Domanda

Ho voluto discutere di un uso specifico che ho di una mappa concorrente a controllare il mio senso di logica ...

Se ho usato ConcurrentHashMap, posso fare la familiarità

private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<K, V>();

public V getExampleOne(K key) {
    map.putIfAbsent(key, new Object());
    return map.get(key);
}

ma mi rendo conto che una condizione gara esiste per cui se rimuovere l'elemento dalla mappa tra il putIfAbsent e get, il metodo di cui sopra potrebbe restituire qualcosa che non esiste più nella collezione. Questo può o non può andare bene, ma lascia supporre che per il mio caso d'uso, non è ok.

Quello che mi piace molto è quello di avere il tutto atomica. Così,

public V getExampleTwo(K key) {
    return map.putIfAbsent(key, new Object());
}

ma come questo si espande verso

if (!map.containsKey(key))
   return map.put(key, value);     [1]
return map.get(key);

che per la linea [1] tornerà null per il primo utilizzo (cioè, map.put restituirà il valore precedente, che per il primo uso è null).

non posso averlo restituire nulla in questo caso

Il che mi lascia con qualcosa di simile;

public V getExampleThree(K key) {
    Object object = new Object();
    V value = locks.putIfAbsent(key, object);
    if (value == null)
        return object;
    return value;
}

Così, finalmente, il mio questione; come fanno gli esempi sopra differiscono semantica ?. Ha getExampleThree garantire atomicità come getExampleTwo ma evitare correttamente il ritorno nullo? Ci sono altri problemi con getExampleThree?

speravo in un po 'di discussione intorno alle scelte. Mi rendo conto che potrei usare un non ConcurrentHashMap e sincronizzare in giro i clienti di chiamare il mio metodo get e un metodo per rimuovere dalla mappa ma che sembra sconfiggere lo scopo (blocco natura non) del ConcurrentHashMap. è che la mia unica scelta di mantenere i dati accurate

Credo che sia un po 'di parte perché ci si sceglie ConcurrentHashMap; che la sua / visibile up-to-date / acurrate nel punto in cui interagiscono con esso, ma ci può essere un impatto più in basso la linea se vecchi dati sta per essere un problema ...

È stato utile?

Soluzione

Sembra che si sta tentando di creare un oggetto blocco globale per una chiave.

Invece di eliminare una voce con la possibilità di farlo ricreato quasi subito, vorrei eliminare solo la voce quando si abbastanza sicuro che la sua non è mai più bisogno.

Altrimenti, se si utilizza questo per un blocco, si possono avere due bloccaggio del filetto su oggetti diversi per lo stesso tasto.


Se la sua non è ok, si può occupato loop.

public V getExampleOne(K key) {
    for(Object o = null, ret = null; (ret = map.get(key)) == null; )
        map.putIfAbsent(key, o == null ? o = new Object() : o);
    return ret;
}

può ancora essere rimosso o sostituito non appena si verifica l'anello così la sua efficacia molto stesso.

public V getExampleThree(K key) {
    Object o = new Object();
    map.putIfAbsent(key, o);
    Object ret = map.get(key);
    return ret == null ? o : ret;
}

Così, finalmente, la mia domanda; come fanno gli esempi sopra riportati si differenziano per la semantica?.

La differenza è solo l'ovvio.

non getExampleThree garantire atomicità come getExampleTwo ma evitare correttamente il ritorno nullo?

Sì.

Ci sono altri problemi con getExampleThree?

Solo se si ritiene che il successivo chiamata non si potrebbe dare un valore diverso (se si ritiene che possa essere rimosso in un altro thread)

Altri suggerimenti

I metodi hanno una semantica diversa:

  • getExampleOne non è atomica.
  • getExampleTwo restituisce null se il nuovo oggetto è stato inserito nel programma. Ciò differisce dal comportamento di getExampleOne, ma è atomico.
  • getExampleThree è probabilmente quello che volete. E 'atomica e restituire l'oggetto che è nella mappa dopo il momento della chiamata putIfAbsent. Ma è stato problema quando nulli sono valori validi nella vostra applicazione. Il valore restituito null è quindi ambiguo.

Tuttavia, a seconda della situazione potrebbe non essere l'oggetto reale nel momento in cui si utilizza il valore di ritorno. È quindi necessario blocco esplicito.

Perché non è sufficiente utilizzare la prima versione e sincronizzare il metodo?

public synchronized V getExampleOne(K key) {
    map.putIfAbsent(key, new Object());
    return map.get(key);
}

Anche se non vi fornirà il massimo parallelismo, è anche vero che hai solo due operazioni e getExampleThree, mentre corretto, è meno leggibile e meno facile da capire per qualcun altro leggere il vostro codice.

Credo che troverete il trucco è quello di assumere sarà non-atomico e gestirlo.

Non sono affatto chiaro cosa state cercando. Fatemi sapere se questo è per la tangente e io modifico.

Forse siete alla ricerca di qualcosa di simile a:

private final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap();

/*
 * Guaranteed to return the object replaced.
 * 
 * Note that by the time this method returns another thread 
 * may have already replaced the object again.
 * 
 * All we can guarantee here is that the return value WAS 
 * associated with the key in the map at the time the entry was 
 * replaced.
 * 
 * A null return implies that either a null object was associated
 * with the specified key or there was no entry in the map for 
 * the specified key wih 'null' as it's 'value'.
 */
public Object consistentReplace ( String key, Object newValue ) {
  Object oldValue = map.get(key);
  while ( !map.replace(key, oldValue, newValue) ) {
    // Failed to replace because someone got in there before me.
    oldValue = map.get(key);
  }
  return oldValue;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top