2012-08-27 13 views
9

Sto provando a creare un Map con i valori int e aumentarli di più thread. due o più thread potrebbero aumentare la stessa chiave.aggiornamento simultaneo java map

ConcurrentHashMap documentazione è stato molto chiaro per me dal momento che sais che:

Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove)

mi chiedo se il seguente codice utilizzando ConcurrentHashMap volontà funziona correttamente:

myMap.put(X, myMap.get(X) + 1);

se non come posso gestire una cosa del genere?

+0

In Java 8, questo può tranquillamente essere fatto con 'myMap.merge (X, 1, Integer: : sum) '. – shmosel

risposta

9

La mappa concomitante non aiuta la sicurezza del thread del codice. È ancora possibile ottenere condizioni di gara:

Thread-1: x = 1, get(x) 
Thread-2: x = 1, get(x) 
Thread-1: put(x + 1) => 2 
Thread-2: put(x + 1) => 2 

Si sono verificati due incrementi, ma si ottiene solo +1. Hai bisogno di una mappa concorrente solo se miri a modificare la mappa stessa, non il suo contenuto. Anche il più semplice HashMap è threadsafe for concurrent reads, data la mappa non viene più modificata.

Quindi, invece di una mappa a prova di bug per il tipo primitivo, è necessario un wrapper thread-safe per il tipo. O qualcosa da java.util.concurrent.atomic o arrotolare il proprio contenitore bloccato se si ha bisogno di un tipo arbitrario.

1

Si potrebbe semplicemente mettere l'operazione in un blocco .

3

Un'idea sarebbe combinare ConcurrentMap con AtomicInteger, che ha un metodo di incremento.

AtomicInteger current = map.putIfAbsent(key, new AtomicInteger(1)); 
int newValue = current == null ? 1 :current.incrementAndGet(); 

o (in modo più efficiente, grazie @Keppil) con una guardia codice aggiuntivo per evitare la creazione di oggetti inutili:

AtomicInteger current = map.get(key); 
if (current == null){ 
    current = map.putIfAbsent(key, new AtomicInteger(1)); 
} 
int newValue = current == null ? 1 : current.incrementAndGet(); 
+0

se si utilizza una ConcurrentMap non è necessario utilizzare anche interi atomici, per questo è concurrentHashMap.replace (K, V, V). – jolivier

+0

Se gli aggiornamenti sono rari, anche un semplice sincronizzato come @dflemstr suggerisce. Non sei sicuro di quanto tempo è necessario per giustificare AtomicInteger. – Thilo

+1

@jolivier 'replace' è soggetto a riprovare, mentre' getAndIncrement' non lo è. –

0

il codice corrente modifica i valori della tua mappa in concomitanza quindi questo non funzionerà.

Se più thread possono valori put nella mappa, è necessario utilizzare una mappa concorrente come ConcurrentHashMap con valori non thread safe come Integer. ConcurrentMap.replace farà ciò che vuoi (o usa AtomicInteger per facilitare il tuo codice).

Se le discussioni cambieranno solo i valori (e non aggiungere/modificare i tasti) della mappa, è possibile utilizzare una serie mappa memorizzazione filo valori di sicurezza come AtomicInteger. Quindi il tuo thread chiamerà: map.get(key).incrementAndGet() per esempio.

2

Buone pratiche. È possibile utilizzare HashMap e AtomicInteger.Codice Test:

public class HashMapAtomicIntegerTest { 
    public static final int KEY = 10; 

    public static void main(String[] args) { 
     HashMap<Integer, AtomicInteger> concurrentHashMap = new HashMap<Integer, AtomicInteger>(); 
     concurrentHashMap.put(HashMapAtomicIntegerTest.KEY, new AtomicInteger()); 
     List<HashMapAtomicCountThread> threadList = new ArrayList<HashMapAtomicCountThread>(); 
     for (int i = 0; i < 500; i++) { 
      HashMapAtomicCountThread testThread = new HashMapAtomicCountThread(
        concurrentHashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       HashMapAtomicCountThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" 
       + concurrentHashMap.get(HashMapAtomicIntegerTest.KEY)); 
    } 
} 

class HashMapAtomicCountThread extends Thread { 
    HashMap<Integer, AtomicInteger> concurrentHashMap = null; 

    public HashMapAtomicCountThread(
      HashMap<Integer, AtomicInteger> concurrentHashMap) { 
     this.concurrentHashMap = concurrentHashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      concurrentHashMap.get(HashMapAtomicIntegerTest.KEY) 
        .getAndIncrement(); 
     } 
    } 
} 

Risultati:

Il valore del risultato dovrebbe essere 5000000, in realtà is5000000

O HashMap e sincronizzati, ma molto più lento rispetto l'ex

public class HashMapSynchronizeTest { 

    public static final int KEY = 10; 

    public static void main(String[] args) { 

     HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>(); 
     hashMap.put(KEY, 0); 
     List<HashMapSynchronizeThread> threadList = new ArrayList<HashMapSynchronizeThread>(); 
     for (int i = 0; i < 500; i++) { 
      HashMapSynchronizeThread testThread = new HashMapSynchronizeThread(
        hashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       HashMapSynchronizeThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" + hashMap.get(KEY)); 
    } 
} 

class HashMapSynchronizeThread extends Thread { 
    HashMap<Integer, Integer> hashMap = null; 

    public HashMapSynchronizeThread(
      HashMap<Integer, Integer> hashMap) { 
     this.hashMap = hashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      synchronized (hashMap) { 
       hashMap.put(HashMapSynchronizeTest.KEY, 
         hashMap 
           .get(HashMapSynchronizeTest.KEY) + 1); 
      } 
     } 
    } 
} 

Risultati:

Il valore del risultato dovrebbe essere 5000000, in realtà is5000000

Usa ConcurrentHashMap otterrà i risultati errati.

public class ConcurrentHashMapTest { 

    public static final int KEY = 10; 

    public static void main(String[] args) { 
     ConcurrentHashMap<Integer, Integer> concurrentHashMap = new ConcurrentHashMap<Integer, Integer>(); 
     concurrentHashMap.put(KEY, 0); 
     List<CountThread> threadList = new ArrayList<CountThread>(); 
     for (int i = 0; i < 500; i++) { 
      CountThread testThread = new CountThread(concurrentHashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       CountThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" + concurrentHashMap.get(KEY)); 
    } 
} 

class CountThread extends Thread { 
    ConcurrentHashMap<Integer, Integer> concurrentHashMap = null; 

    public CountThread(ConcurrentHashMap<Integer, Integer> concurrentHashMap) { 
     this.concurrentHashMap = concurrentHashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      concurrentHashMap.put(ConcurrentHashMapTest.KEY, 
        concurrentHashMap.get(ConcurrentHashMapTest.KEY) + 1); 
     } 
    } 
} 

Risultati:

Il valore del risultato dovrebbe essere 5000000, in realtà is11759

+0

Puoi imparare il principio dalla risposta di @vtmarvin – wodong