Variables atómicas

Mediante el uso de variables atómicas podemos asegurarnos de que el cambio de valor en una variable en un programa multihilo, o se produce completamente, o falla de una forma clara. Además, no se puede quedar a medias ni acceder a medio modificar por parte de otro hilo. Una alternativa al uso de synchronized y volatile.

Cuando trabajamos con programación concurrente en Java, uno de los problemas más comunes que enfrentamos es la corrupción de memoria al modificar variables compartidas desde múltiples hilos. Por ejemplo, imaginemos que tenemos una variable total que queremos incrementar 10.000 veces desde dos hilos diferentes. Intuitivamente, podríamos pensar que el resultado final será 20.000, pero debido a la naturaleza de la concurrencia, esto no siempre sucede. La razón es que ambos hilos pueden intentar actualizar la variable al mismo tiempo, lo que provoca que algunas actualizaciones se pierdan y el valor final sea incorrecto.

Para evitar este problema, tradicionalmente hemos utilizado mecanismos como synchronized o locks, que garantizan que solo un hilo pueda acceder a la variable a la vez. Sin embargo, en algunos casos no es posible modificar el método para añadir sincronización o no queremos introducir la sobrecarga que esto implica. Aquí es donde entran en juego las clases atómicas del paquete java.util.concurrent.atomic.

Estas clases, como AtomicInteger, AtomicBoolean o AtomicReference, nos ofrecen variables que se pueden modificar de forma segura en entornos multihilo sin necesidad de sincronización explícita. Por ejemplo, AtomicInteger es una alternativa segura a un entero primitivo cuando queremos hacer operaciones atómicas como incrementar o decrementar su valor.

Veamos algunos de los métodos que nos proporciona AtomicInteger. Tenemos incrementAndGet(), que incrementa el valor y devuelve el resultado, o addAndGet(int delta), que suma un valor dado y devuelve el nuevo resultado. También existen métodos como decrementAndGet(), getAndIncrement() o getAndDecrement(), que varían en el orden en que realizan la operación y devuelven el valor antes o después del cambio. Además, podemos usar updateAndGet() para aplicar una función lambda que modifique el valor de forma atómica.

Lo importante es que estas operaciones son atómicas, es decir, se ejecutan como una única acción indivisible. Esto garantiza que no se interrumpa la operación por otro hilo, evitando así la corrupción de la variable compartida. Por ejemplo, si usamos un AtomicInteger para incrementar la variable total desde dos hilos, el resultado final será siempre el esperado, 20.000, porque cada incremento se realiza de forma segura.

Un detalle a tener en cuenta es que las variables atómicas no son tipos primitivos, por lo que no podemos usar operadores aritméticos directamente sobre ellas. En lugar de total++, debemos usar total.incrementAndGet() o total.addAndGet(1) para modificar su valor.

Además de AtomicInteger, existen otras clases atómicas para diferentes tipos de datos. AtomicBoolean nos permite manejar valores booleanos de forma segura, con métodos como get(), set(), o getAndSet(), que devuelve el valor anterior y establece uno nuevo. También podemos encontrar AtomicLong para valores largos y AtomicReference para referencias a objetos, que nos permiten hacer operaciones atómicas sobre cualquier tipo de dato.

Para trabajar con colecciones atómicas, el paquete ofrece clases específicas que garantizan la seguridad en entornos concurrentes, pero eso es tema para otra ocasión.

En resumen, las variables atómicas son una herramienta muy útil para manejar datos compartidos en programas multihilo, evitando la corrupción de memoria sin necesidad de usar sincronización explícita y con un rendimiento generalmente mejor. Usarlas correctamente nos permite escribir código concurrente más seguro y eficiente.

Lista de reproducción
  1. 1
    ¿Qué es la concurrencia?
    8 minutos
  2. 2
    ¿Qué es un hilo?
    5 minutos
  3. 3
    Crear un hilo en Java
    10 minutos
  4. 4
    Interrupción de hilos
    10 minutos
  5. 5
    Cómo usar Thread.join
    14 minutos
  6. 6
    Corrupción de memoria
    8 minutos
  7. 7
    Monitores y synchronized
    10 minutos
  8. 8
    Bloque synchronized
    12 minutos
  9. 9
    Preguntas típicas sobre synchronized en Java
    8 minutos
  10. 10
    Interbloqueos, synchronized y el problema de la cena de los filósofos
    13 minutos
  11. 11
    Introducción a Executor en Java
    11 minutos
  12. 12
    Introducción al uso de ExecutorService en Java
    14 minutos
  13. 13
    Introducción a Thread Pools en Java
    10 minutos
  14. 14
    Cómo crear Thread Pools en Java
    11 minutos
  15. 15
    ¿Cómo funcionan wait() y notify()?
    10 minutos
  16. 16
    Ejemplo de wait() y notify() en Java
    9 minutos
  17. 17
    La palabra clave volatile
    8 minutos
  18. 18
    Cómo usar Future
    8 minutos
  19. 19
    Variables atómicas
    7 minutos