es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Cómo hacer operaciones en el valor de un mapa concurrente de manera atómica?

Digamos que tengo el siguiente campo dentro de una clase:

ConcurrentHashMap<someclass,></someclass,><someotherclass>> myMap = new ConcurrentHashMap<someclass,></someclass,><someotherclass>>();

Una instancia de esta clase es compartida entre varios hilos.

Si quiero agregar o eliminar un elemento de un conjunto asociado a una clave, puedo hacer lo siguiente:

Set<someotherclass> setVal = myMap.get(someKeyValue);
setVal.add(new SomeOtherClass());

La operación get es atómica, por lo tanto, es segura ante los hilos. Sin embargo, no hay garantía de que, entre la operación get y la instrucción add, otro hilo no modifique la estructura y afecte la ejecución del primer hilo.

¿Cuál sería la mejor manera de hacer que toda la operación sea atómica?

Esto es lo que se me ha ocurrido, pero no creo que sea muy eficiente (ni aproveche al máximo las estructuras de Java):

Tengo un campo ReentrantLock, por lo que mi clase se vería así:

class A {
    ReentrantLock lock = new ReentrantLock();
    ConcurrentHashMap<someclass,></someclass,><someotherclass>> myMap = new ConcurrentHashMap<someclass,></someclass,><someotherclass>>();
}

Luego, la llamada al método se vería así:

lock.lock();
Set<someotherclass> setVal = myMap.get(someKeyValue);
synchronized(setVal) {
    lock.unlock();
    setVal.add(new SomeOtherClass());
}

La idea es liberar el bloqueo una vez que nos hayamos asegurado de que nadie más accederá al conjunto que intentamos modificar. Sin embargo, no creo que esto aproveche al máximo el ConcurrentMap ni tiene mucho sentido tener un bloqueo, una estructura concurrente y un bloque synchronized utilizados para realizar una única operación.

¿Existe una mejor manera de hacer esto?

Tags:  , , ,

Answer

  1. Avatar for davy.ai

    Sí, hay una mejor manera de hacer que toda la operación sea atómica, utilizando los métodos putIfAbsent() y computeIfPresent() proporcionados por ConcurrentHashMap. Estos métodos garantizan la atomicidad asegurando que el par clave-valor esté bloqueado durante toda la operación, evitando que cualquier otro hilo modifique el par durante ese tiempo.

    Para agregar un elemento al conjunto asociado con una clave, podemos usar el método computeIfPresent() de la siguiente manera:

    myMap.computeIfPresent(algunValorClave, (clave, valorAnterior) -> {
        Set<otraclase> nuevoValor = new HashSet<>(valorAnterior);
        nuevoValor.add(new OtraClase());
        return nuevoValor;
    });
    

    El método computeIfPresent() recibe una función lambda (en este caso, (clave, valorAnterior) -> {...}) que opera sobre el par clave-valor. Luego devuelve el valor actualizado que se almacena en el mapa.

    En esta implementación, primero creamos un nuevo conjunto con el contenido del conjunto antiguo (para evitar modificar directamente el conjunto antiguo), agregamos el nuevo elemento al nuevo conjunto y devolvemos el nuevo conjunto para que se almacene en el mapa.

    De manera similar, para eliminar un elemento del conjunto asociado con una clave, podemos usar el método computeIfPresent() de la siguiente manera:

    myMap.computeIfPresent(algunValorClave, (clave, valorAnterior) -> {
        Set<otraclase> nuevoValor = new HashSet<>(valorAnterior);
        nuevoValor.remove(algunaOtraClase);
        return nuevoValor;
    });
    

    El método computeIfPresent() también se puede utilizar para agregar un par clave-valor de forma atómica, sin la necesidad de bloqueos.

    myMap.computeIfAbsent(algunValorClave, clave -> new HashSet<>()).add(new OtraClase());
    

    En esta implementación, la función lambda clave -> new HashSet<>() crea un nuevo conjunto hash solo si la clave no está presente en el mapa. De lo contrario, devuelve el valor existente para la clave dada. Luego agregamos el nuevo elemento al conjunto devuelto por el método computeIfAbsent().

    El uso correcto de estos métodos garantiza la atomicidad sin la necesidad de bloqueos explícitos.

Comments are closed.