12

Questa potrebbe essere una domanda doppia, ma ho trovato questa parte di codice in un libro sulla concorrenza. Questo si dice che sia thread-safe:Atomica Java ConcurrentHashMap atomicity

ConcurrentHashMap<String, Integer> counts = new ...; 

private void countThing(String thing) { 
    while (true) { 
     Integer currentCount = counts.get(thing); 
     if (currentCount == null) { 
      if (counts.putIfAbsent(thing, 1) == null) 
       break; 
     } else if (counts.replace(thing, currentCount, currentCount + 1)) { 
      break; 
     } 
    } 
} 

Dalle mie (principianti concorrenza) punto di vista, filo t1 e filo t2 poteva sia in lettura currentCount = 1. Quindi entrambi i thread potrebbero cambiare il valore delle mappe a 2. Qualcuno può spiegarmi se il codice è ok o no?

risposta

11

Il trucco è che replace(K key, V oldValue, V newValue) fornisce l'atomicità per te. Da the docs (sottolineatura mia):

Sostituisce la voce per una chiave solo se attualmente mappata a un dato valore. ... l'azione viene eseguita atomicamente.

La parola chiave è "atomicamente". All'interno di replace, il "controllo se il vecchio valore è quello che ci aspettiamo, e solo se lo è, sostituirlo" si presenta come una singola porzione di lavoro, senza altri thread in grado di interagire con esso. Spetta all'implementazione eseguire qualsiasi sincronizzazione necessaria per assicurarsi che fornisca questa atomicità.

Quindi, non è possibile che entrambi i thread vedano currentAction == 1 all'interno della funzione replace. Uno di loro lo vedrà come 1, e quindi la sua chiamata a replace restituirà true. L'altro lo vedrà come 2 (a causa della prima chiamata), quindi restituirà false — e tornerà indietro per riprovare, questa volta con il nuovo valore di currentAction == 2.

Ovviamente, è possibile che un terzo thread abbia aggiornato currentAction su 3 nel frattempo, nel qual caso quel secondo thread continuerà a provare finché non sarà abbastanza fortunato da non dover essere preceduto da nessuno.

+0

ringrazio molto, credo di capire ora. :) –

-1

Con put è possibile anche sostituire il valore.

if (currentCount == null) { 
     counts.put(thing, 2); 
    } 
6

Qualcuno può spiegare a me se il codice va bene o no?

Oltre alla risposta di yshavit, si può evitare di scrivere il proprio ciclo utilizzando compute che è stato aggiunto in Java 8.

ConcurrentMap<String, Integer> counts = new ...; 

private void countThing(String thing) { 
    counts.compute(thing, (k, prev) -> prev == null ? 1 : 1 + prev); 
} 
+0

Cool, grazie! :) –