2013-02-13 13 views
11

Ho un ConcurrentHashMap dove faccio la seguente:Utilizzo di ConcurrentHashMap, quando è necessaria la sincronizzazione?

sequences = new ConcurrentHashMap<Class<?>, AtomicLong>(); 

if(!sequences.containsKey(table)) { 
    synchronized (sequences) { 
     if(!sequences.containsKey(table)) 
      initializeHashMapKeyValue(table); 
    } 
} 

La mia domanda è - è inutile introdurre l'extra

if(!sequences.containsKey(table)) 

Controllare all'interno del blocco synschronized così altri thread solito inizializzare lo stesso HashMap valore?

Forse il controllo è necessario e sto sbagliando? Sembra un po 'sciocco quello che sto facendo, ma penso sia necessario.

+0

Quando si desidera ottenere ConcurrentHashMap, qual è il requisito del blocco di oggetti sincronizzati su questa istanza. – Anil

+0

Sì, è necessario. – assylias

risposta

20

Tutte le operazioni di su ConcurrentHashMap sono thread-safe, ma le operazioni thread-safe non sono componibili. Stai cercando di fare atomico un paio di operazioni: controllare qualcosa nella mappa e, nel caso in cui non ci sia, metti qualcosa lì (presumo). Quindi la risposta alle tue domande è , devi ricontrollare e il tuo codice sembra ok.

+1

L'unico caso in cui si può saltare il controllo extra è quando non si cura che il valore di 'initializeHashMapKeyValue (table)' sarà più di una volta per lo stesso valore 'table'. Questo potrebbe essere dovuto al fatto che 'initializeHashMapKeyValue' non ha effetti collaterali ed è abbastanza economico. –

3

Non è possibile ottenere il blocco esclusivo con ConcurrentHashMap. In tal caso, è meglio usare HashMap sincronizzato.

Esiste già un metodo atomico da inserire in ConcurrentHashMap se l'oggetto non è già lì; putIfAbsent

+1

Il fatto che non sia possibile ottenere un blocco sulla mappa non impedisce di sincronizzare l'accesso alla mappa. – assylias

+0

d'accordo ... ma aggiunge qualsiasi valore. In effetti avrai un'impressione sbagliata. –

+1

ConcurrentMap otterrà una concorrenza molto migliore, quindi se 'sequences.containsKey (table)' è generalmente vero, l'uso di una mappa simultanea ha ancora senso se le prestazioni sono un problema. – assylias

1

Vedo cosa hai fatto lì ;-) la domanda è la vedi tu stesso?

Prima di tutto si utilizzava qualcosa chiamato "Motivo di blocco a doppio controllo". Dove si trova il percorso veloce (prima contiene) che non richiede sincronizzazione se il caso è soddisfatto e il percorso lento che deve essere sincronizzato perché si esegue un'operazione complessa. L'operazione consiste nel controllare se qualcosa è all'interno della mappa e quindi inserirvi qualcosa/inizializzarlo. Quindi non importa che ConcurrentHashMap sia thread-safe per singola operazione perché fai due semplici operazioni che devono essere trattate come unità, quindi sì, questo blocco sincronizzato è corretto e in realtà potrebbe essere sincronizzato da qualsiasi altra cosa, ad esempio this.

16

Si dovrebbero usare i metodi putIfAbsent di ConcurrentMap.

ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>(); 

public long addTo(String key, long value) { 
    // The final value it became. 
    long result = value; 
    // Make a new one to put in the map. 
    AtomicLong newValue = new AtomicLong(value); 
    // Insert my new one or get me the old one. 
    AtomicLong oldValue = map.putIfAbsent(key, newValue); 
    // Was it already there? Note the deliberate use of '!='. 
    if (oldValue != newValue) { 
    // Update it. 
    result = oldValue.addAndGet(value); 
    } 
    return result; 
} 

Per i puristi funzionali tra noi, quanto sopra può essere semplificata (o forse complessizzate) a:

public long addTo(String key, long value) { 
    return map.putIfAbsent(key, new AtomicLong()).addAndGet(value); 
} 

E in Java 8 siamo in grado di evitare l'inutile creazione di un AtomicLong:

public long addTo8(String key, long value) { 
    return map.computeIfAbsent(key, k -> new AtomicLong()).addAndGet(value); 
} 
+1

Potrebbe essere sensato usare lo schema dell'OP se 'initializeHashMapKeyValue' prende una quantità di tempo non trascurabile o ha effetti collaterali. – assylias

+0

@assylias - Se gli effetti collaterali sono inevitabili, penso che sarei d'accordo ma ci sono molti metodi per evitare/aggirare gli effetti collaterali usando i metodi di fabbrica et-al. Userei questo schema ovunque possibile perché è garantito che sia privo di condizioni di gara mentre il pattern a doppio quadrilatero è famoso per avere problemi molto sottili. – OldCurmudgeon

+1

Il blocco a doppio controllo può presentare problemi, ma non in questo specifico esempio. Nota che non sto dicendo che il tuo approccio non sia appropriato - sto solo dicendo che l'approccio dell'OP potrebbe essere migliore in alcune circostanze. – assylias

1

In Java 8 si dovrebbe essere in grado di sostituire il blocco con doppio controllo con .computeIfAbsent:

sequences.computeIfAbsent(table, k -> initializeHashMapKeyValue(k)); 
0

Creare un file denominato dizionario.txt con i seguenti contenuti:

a 
as 
an 
b 
bat 
ball 

Qui abbiamo: conte di parole che iniziano con "a": 3

Conte di parole che iniziano con "b": 3

parola totale contare: 6

Ora eseguire il seguente programma come: java WordCount test_dictionary.txt 10

public class WordCount { 
String fileName; 

public WordCount(String fileName) { 
    this.fileName = fileName; 
} 

public void process() throws Exception { 
    long start = Instant.now().toEpochMilli(); 

    LongAdder totalWords = new LongAdder(); 
    //Map<Character, LongAdder> wordCounts = Collections.synchronizedMap(new HashMap<Character, LongAdder>()); 
    ConcurrentHashMap<Character, LongAdder> wordCounts = new ConcurrentHashMap<Character, LongAdder>(); 

    Files.readAllLines(Paths.get(fileName)) 
     .parallelStream() 
     .map(line -> line.split("\\s+")) 
     .flatMap(Arrays::stream) 
     .parallel() 
     .map(String::toLowerCase) 
     .forEach(word -> { 
      totalWords.increment(); 
      char c = word.charAt(0); 
      if (!wordCounts.containsKey(c)) { 
       wordCounts.put(c, new LongAdder()); 
      } 
      wordCounts.get(c).increment(); 
     }); 
    System.out.println(wordCounts); 
    System.out.println("Total word count: " + totalWords); 

    long end = Instant.now().toEpochMilli(); 
    System.out.println(String.format("Completed in %d milliseconds", (end - start))); 
} 

public static void main(String[] args) throws Exception { 
    for (int r = 0; r < Integer.parseInt(args[1]); r++) { 
     new WordCount(args[0]).process(); 
    } 
} 

}

vedresti conta variano come illustrato di seguito:

{a = 2, b = 3}

parola conteggio totale: 6

completata nel 77 millisecondi

{a = 3, b = 3}

Numero totale di parole: 6

Ora commenta ConcurrentHashMap alla riga 13, decommenta la riga sopra di esso ed esegui di nuovo il programma.

Si vedrebbero i conteggi deterministici.

Problemi correlati