2012-08-27 30 views
9

Estoy tratando de crear un Map con int valores y aumentarlos por varios subprocesos. dos o más hilos pueden aumentar la misma clave.java map concurrent update

documentación

ConcurrentHashMap era muy claro para mí, ya que EFS que:

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

Me pregunto si el siguiente código utilizando ConcurrentHashMap voluntad trabaja correctamente:

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

si no se ¿Cómo puedo gestionar tal cosa?

+0

En Java 8, esto puede ejecutarse con plena seguridad con 'myMap.merge (X, 1, Entero: : suma) '. – shmosel

Respuesta

9

El mapa simultáneo no ayudará a proteger la seguridad de su código. Usted todavía puede obtener condición de carrera:

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

dos incrementos ocurrieron, pero aún así obtener sólo 1. Necesita un mapa simultáneo solo si tiene como objetivo modificar el mapa en sí, no su contenido. Incluso el más simple HashMap es threadsafe for concurrent reads, dado que el mapa ya no está mutado.

Por lo tanto, en lugar de un mapa de hilos para tipo primitivo, necesita una envoltura de hilo para el tipo. O algo del java.util.concurrent.atomic o ruede su propio contenedor bloqueado si necesita un tipo arbitrario.

1

Puede simplemente poner la operación en un bloque synchronized (myMap) {...}.

3

Una idea sería combinar ConcurrentMap con AtomicInteger, que tiene un método de incremento.

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

o (de manera más eficiente, gracias @Keppil) con un guardia de código extra para evitar la innecesaria creación de objetos:

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

si usa un ConcurrentMap no tiene sentido usar también números enteros atómicos, ConcurrentHashMap.replace (K, V, V) está hecho para esto. – jolivier

+0

Si las actualizaciones son raras, un simple sincronizado como @dflemstr sugiere que también funcionará. No estoy seguro de cuánto rendimiento necesita para justificar AtomicInteger. – Thilo

+1

@jolivier 'replace' está sujeto a reintentar, mientras que' getAndIncrement' no lo está. –

0

su código actual cambia los valores de su mapa al mismo tiempo para que esto no va a funcionar.

Si múltiples hilos pueden put valores en su mapa, tiene que usar un mapa simultáneo como ConcurrentHashMap con valores no seguros de subprocesos como Integer. ConcurrentMap.replace hará lo que quiera (o use AtomicInteger para facilitar su código).

Si sus hilos sólo cambiarán los valores (y no añadir/cambiar las claves) de su mapa, a continuación, se puede utilizar un mapa estándar que almacenan valores seguros hilo como AtomicInteger. Entonces su hilo llamará: map.get(key).incrementAndGet() por ejemplo.

2

Mejor práctica. Puede usar HashMap y AtomicInteger.código prueba:

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(); 
     } 
    } 
} 

Resultados:

El valor resultante debe ser 5000000, en realidad is5000000

O HashMap y sincronizados, pero mucho más lento que el anterior

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); 
      } 
     } 
    } 
} 

Resultados:

El valor resultado debe ser 5000000, en realidad is5000000

Uso ConcurrentHashMap obtendrán resultados erróneos.

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); 
     } 
    } 
} 

Resultados:

El valor resultado debe ser 5000000, en realidad is11759

+0

Puedes aprender el principio de la respuesta de @vtmarvin – wodong