Agregar a AtomicInteger dentro de ConcurrentHashMap - java, multithreading, concurrency, java.util.concurrent, concurrenthashmap

Tengo los siguientes definidos

private ConcurrentMap<Integer, AtomicInteger>  = new ConcurrentHashMap<Integer, AtomicInteger>();

private void add() {
staffValues.replace(100, staffValues.get(100), new AtomicInteger(staffValues.get(100).addAndGet(200)));
}

Después de la prueba, los valores que obtengo no sonesperado, y creo que hay una condición de carrera aquí. ¿Alguien sabe si esto se consideraría enhebrable envolviendo la llamada de obtención en la función de reemplazo?

Respuestas

2 para la respuesta № 1

Hay algunos problemas con su código. Lo más importante es que estás ignorando el valor de retorno de ConcurrentHashMap.replace: si el reemplazo no ocurre (debido a que otro hilo ha hecho un reemplazo en paralelo), simplemente proceda como si ocurrió. Esta es la razón principal por la que obtiene resultados incorrectos.

También creo que es un error de diseño mutar un AtomicInteger y luego reemplazarlo de inmediato con un diferente AtomicInteger; incluso si puede hacer que esto funcione, simplemente no hay ninguna razón para ello.

Por último, no creo que debas llamar staffValues.get(100) dos veces. No creo que cause un error en el código actual; su corrección depende solo de que la segunda llamada devuelva un resultado "más nuevo" que el primero, lo que creo es en realidad garantizado por ConcurrentHashMap - pero es frágil, sutil y confuso. En general, cuando llamas ConcurrentHashMap.replace, su tercer argumento debería ser algo que computaste usando el segundo.

En general, puede simplificar su código ya sea al no usar AtomicInteger:

private ConcurrentMap<Integer, Integer> staffValues = new ConcurrentHashMap<>();

private void add() {
final Integer prevValue = staffValues.get(100);
staffValues.replace(100, prevValue, prevValue + 200);
}

o al no usar replace (y quizás ni siquiera ConcurrentMap, dependiendo de qué otra cosa estés tocando este mapa):

private Map<Integer, AtomicInteger> staffValues = new HashMap<>();

private void add() {
staffValues.get(100).addAndGet(200);
}

1 para la respuesta № 2

Una buena forma de manejar situaciones como esta es usar el computeIfAbsent método (no el compute método que recomienda @ the8472)

los computeIfAbsent acepta 2 argumentos, la clave y un Function<K, V> eso solo se llamará si falta el valor existente. Como AtomicInteger es seguro para subprocesos a partir de varios subprocesos, puede usarlo fácilmente de la siguiente manera:

staffValues.computeIfAbsent(100, k -> new AtomicInteger(0)).addAndGet(200);

0 para la respuesta № 3

No necesita usar replace(). AtomicInteger es un valor mutable que no necesita ser sustituido cada vez que desee incrementarlo. De hecho addAndGet ya lo incrementa en su lugar.

En su lugar use compute para poner un valor predeterminado (presumiblemente 0) en el mapa cuando no está presente y obtener el valor preexistente e incrementarlo.

Si, por otro lado, quieres usar valores inmutables puestos Integer instancias en lugar de AtomicInteger en el mapa y actualizarlos con las operaciones atómicas de cálculo / reemplazo / fusión.


Menú