Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Aprende Variables Atómicas | Mecanismos de Sincronización de Alto Nivel
Multithreading en Java

bookVariables Atómicas

Ya hemos cubierto qué es la atomicidad y los problemas que puede causar en la primera sección de este curso. En ese momento, abordamos el problema utilizando bloques sincronizados o métodos. Ahora, exploraremos cómo lograr el mismo resultado de manera más sencilla utilizando una clase atómica.

¿Qué son las Variables Atómicas?

Las variables atómicas garantizan que las operaciones (lectura, escritura, incremento) sobre variables se realicen atómicamente, es decir, que se ejecuten de forma continua y segura en un entorno multihilo. Esto asegura que la operación se complete en su totalidad, sin posibilidad de interferencia de otros hilos durante su ejecución.

¿Por qué necesitamos variables atómicas?

Sin el uso de variables atómicas u otros mecanismos de sincronización, operaciones como incrementar (++) pueden no ser seguras. Por ejemplo, cuando múltiples hilos acceden a la misma variable simultáneamente, las actualizaciones pueden perderse, lo que resulta en resultados incorrectos. Las variables atómicas resuelven este problema al garantizar que las operaciones sobre ellas se ejecuten de forma secuencial.

Anteriormente discutimos este problema cuando la operación de incremento se desglosaba en tres pasos (leer, incrementar, escribir), pero con las variables atómicas, todo se realiza en una sola operación.

Tipos de variables atómicas en Java

Note
Nota

En general, existen muchas implementaciones atómicas, y no las cubriremos todas aquí, ya que tomaría demasiado tiempo.

Java proporciona varias clases de variables atómicas en el paquete java.util.concurrent.atomic, cada una diseñada para manejar un tipo de dato específico:

  • AtomicInteger: para operaciones atómicas sobre int;
  • AtomicLong: para operaciones atómicas sobre long;
  • AtomicBoolean: para operaciones atómicas sobre boolean;
  • AtomicReference<V>: para operaciones atómicas sobre objetos (tipo genérico).

Métodos

El método get() devuelve el valor actual de una variable. El método set(V newValue) asigna un nuevo valor a la variable. Por otro lado, lazySet(V newValue) es similar a set(), pero puede posponer la actualización del valor, ofreciendo una actualización ordenada en ciertas situaciones.

Main.java

Main.java

copy
123456789101112131415161718192021
package com.example; import java.util.concurrent.atomic.AtomicReference; public class Main { public static void main(String[] args) { AtomicReference<String> atomicString = new AtomicReference<>("Initial Value"); // Using `get()` to retrieve the current value String value = atomicString.get(); System.out.println("Current Value: " + value); // Using `set()` to update the value atomicString.set("New Value"); System.out.println("Value after set(): " + atomicString.get()); // Using `lazySet()` to update the value atomicString.lazySet("Lazy Set Value"); System.out.println("Value after lazySet(): " + atomicString.get()); } }

El método compareAndSet(V expect, V update) actualiza el valor si el valor actual coincide con el valor esperado. Devuelve true si la actualización fue exitosa, y false si el valor actual no coincidió con el valor esperado. En cambio, el método getAndSet(V newValue) establece un nuevo valor y devuelve el valor anterior.

Main.java

Main.java

copy
123456789101112131415161718192021222324
package com.example; import java.util.concurrent.atomic.AtomicReference; public class Main { public static void main(String[] args) { // Initialize an `AtomicReference` with an initial value AtomicReference<String> atomicString = new AtomicReference<>("Initial Value"); // Demonstrate `compareAndSet` boolean success = atomicString.compareAndSet("Initial Value", "Updated Value"); System.out.println("compareAndSet success (expected true): " + success); System.out.println("Current value after compareAndSet: " + atomicString.get()); success = atomicString.compareAndSet("Wrong Value", "Another Update"); System.out.println("compareAndSet success (expected false): " + success); System.out.println("Current value after compareAndSet: " + atomicString.get()); // Demonstrate `getAndSet` String previousValue = atomicString.getAndSet("New Value with getAndSet"); System.out.println("Previous value from getAndSet: " + previousValue); System.out.println("Current value after getAndSet: " + atomicString.get()); } }

Los métodos getAndIncrement() y getAndDecrement() incrementan o decrementan el valor actual en uno y devuelven el valor anterior. Estos métodos se aplican a variables atómicas numéricas como AtomicInteger y AtomicLong. En cambio, los métodos incrementAndGet() y decrementAndGet() también incrementan o decrementan el valor actual en uno pero devuelven el nuevo valor.

Main.java

Main.java

copy
123456789101112131415161718192021222324252627282930
package com.example; import java.util.concurrent.atomic.AtomicInteger; public class Main { public static void main(String[] args) { // Initialize `AtomicInteger` with initial value AtomicInteger atomicInt = new AtomicInteger(10); // Demonstrate `getAndIncrement()` for `AtomicInteger` int oldValueInt = atomicInt.getAndIncrement(); System.out.println("Value before getAndIncrement(): " + oldValueInt); // Should print 10 System.out.println("Value after getAndIncrement(): " + atomicInt.get()); // Should print 11 // Demonstrate `getAndDecrement()` for `AtomicInteger` int oldValueIntDec = atomicInt.getAndDecrement(); System.out.println("Value before getAndDecrement(): " + oldValueIntDec); // Should print 11 System.out.println("Value after getAndDecrement(): " + atomicInt.get()); // Should print 10 // Demonstrate `incrementAndGet()` for `AtomicInteger` int newValueInt = atomicInt.incrementAndGet(); System.out.println("Value after incrementAndGet(): " + newValueInt); // Should print 11 System.out.println("Current value after incrementAndGet(): " + atomicInt.get()); // Should print 11 // Demonstrate `decrementAndGet()` for `AtomicInteger` int newValueIntDec = atomicInt.decrementAndGet(); System.out.println("Value after decrementAndGet(): " + newValueIntDec); // Should print 10 System.out.println("Current value after decrementAndGet(): " + atomicInt.get()); // Should print 10 } }

El método getAndAdd(int delta) suma el valor especificado (delta) al valor actual y devuelve el valor anterior. Este método se utiliza con variables atómicas numéricas. Por otro lado, el método addAndGet(int delta) también suma el valor especificado (delta) al valor actual pero devuelve el nuevo valor.

Main.java

Main.java

copy
123456789101112131415161718192021222324252627282930
package com.example; import java.util.concurrent.atomic.AtomicInteger; public class Main { public static void main(String[] args) { // Initialize `AtomicInteger` with an initial value AtomicInteger atomicInt = new AtomicInteger(50); // Demonstrate `getAndAdd(int delta)` int previousValue = atomicInt.getAndAdd(10); System.out.println("Value before getAndAdd(10): " + previousValue); // Should print 50 System.out.println("Value after getAndAdd(10): " + atomicInt.get()); // Should print 60 // Demonstrate `getAndAdd()` with another delta int previousValue2 = atomicInt.getAndAdd(5); System.out.println("Value before getAndAdd(5): " + previousValue2); // Should print 60 System.out.println("Value after getAndAdd(5): " + atomicInt.get()); // Should print 65 // Demonstrate `addAndGet(int delta)` int newValue = atomicInt.addAndGet(20); System.out.println("Value after addAndGet(20): " + newValue); // Should print 85 System.out.println("Current value after addAndGet(20): " + atomicInt.get()); // Should print 85 // Demonstrate `addAndGet()` with another delta int newValue2 = atomicInt.addAndGet(-15); System.out.println("Value after addAndGet(-15): " + newValue2); // Should print 70 System.out.println("Current value after addAndGet(-15): " + atomicInt.get()); // Should print 70 } }

Ejemplos de uso de variables atómicas

Ejemplo con AtomicInteger (AtomicLong, AtomicBoolean funcionan de la misma manera, por lo que no habrá ejemplos separados para ellos).

Tarea: Implementar un contador que sea incrementado de forma segura por varios hilos.

Main.java

Main.java

copy
1234567891011121314151617181920
package com.example; import java.util.concurrent.atomic.AtomicInteger; public class Main { private AtomicInteger counter = new AtomicInteger(0); public void increment() { int oldValue = counter.getAndIncrement(); System.out.println(Thread.currentThread().getName() + ": Counter was " + oldValue + ", now " + counter.get()); } public static void main(String[] args) { Main atomicCounter = new Main(); for (int i = 0; i < 5; i++) { new Thread(atomicCounter::increment).start(); } } }

Como puede observar, no utilizamos ninguna sincronización aquí, ya que la variable atómica proporciona esta función.

Este ejemplo utiliza AtomicInteger para incrementar el contador de forma segura. El método getAndIncrement() primero devuelve el valor actual de la variable y luego lo incrementa en uno. Esto ocurre de manera atómica, asegurando que la variable se actualice correctamente.

Ejemplo con AtomicReference

Tarea: Proporcionar actualización atómica de una referencia a un objeto.

Main.java

Main.java

copy
123456789101112131415161718192021222324
package com.example; import java.util.concurrent.atomic.AtomicReference; public class Main { // `AtomicReference` to safely update and access a shared `String` value private AtomicReference<String> sharedString = new AtomicReference<>("Initial"); public void updateValue(String newValue) { // Atomically sets the new value and gets the old value String oldValue = sharedString.getAndSet(newValue); // Prints the old and new values, along with the thread name System.out.println(Thread.currentThread().getName() + ": Value was " + oldValue + ", now " + sharedString.get()); } public static void main(String[] args) { Main example = new Main(); // Creates and starts 3 threads, each updating the shared value for (int i = 0; i < 3; i++) { new Thread(() -> example.updateValue("Updated by " + Thread.currentThread().getName())).start(); } } }

AtomicReference se utiliza aquí para actualizar atómicamente el valor de una referencia de cadena. El método getAndSet() establece atómicamente el nuevo valor y devuelve el valor anterior.

Note
Nota

A diferencia de las variables regulares, que requieren sincronización adicional, las variables atómicas utilizan primitivas de bajo nivel para minimizar la sobrecarga y mejorar el rendimiento. Esto las hace especialmente adecuadas para sistemas de alta concurrencia.

1. ¿Cuál es la ventaja de utilizar variables atómicas en la programación multihilo?

2. ¿Qué método de variable atómica proporciona un cambio atómico en el valor si el valor actual es igual al valor esperado?

3. ¿Qué garantiza el método set() en las variables atómicas?

question mark

¿Cuál es la ventaja de utilizar variables atómicas en la programación multihilo?

Select the correct answer

question mark

¿Qué método de variable atómica proporciona un cambio atómico en el valor si el valor actual es igual al valor esperado?

Select the correct answer

question mark

¿Qué garantiza el método set() en las variables atómicas?

Select the correct answer

¿Todo estuvo claro?

¿Cómo podemos mejorarlo?

¡Gracias por tus comentarios!

Sección 3. Capítulo 5

Pregunte a AI

expand

Pregunte a AI

ChatGPT

Pregunte lo que quiera o pruebe una de las preguntas sugeridas para comenzar nuestra charla

Suggested prompts:

Can you explain how atomic variables differ from using synchronized blocks?

What are some real-world scenarios where atomic variables are especially useful?

Could you provide a simple code example using AtomicInteger?

Awesome!

Completion rate improved to 3.33

bookVariables Atómicas

Desliza para mostrar el menú

Ya hemos cubierto qué es la atomicidad y los problemas que puede causar en la primera sección de este curso. En ese momento, abordamos el problema utilizando bloques sincronizados o métodos. Ahora, exploraremos cómo lograr el mismo resultado de manera más sencilla utilizando una clase atómica.

¿Qué son las Variables Atómicas?

Las variables atómicas garantizan que las operaciones (lectura, escritura, incremento) sobre variables se realicen atómicamente, es decir, que se ejecuten de forma continua y segura en un entorno multihilo. Esto asegura que la operación se complete en su totalidad, sin posibilidad de interferencia de otros hilos durante su ejecución.

¿Por qué necesitamos variables atómicas?

Sin el uso de variables atómicas u otros mecanismos de sincronización, operaciones como incrementar (++) pueden no ser seguras. Por ejemplo, cuando múltiples hilos acceden a la misma variable simultáneamente, las actualizaciones pueden perderse, lo que resulta en resultados incorrectos. Las variables atómicas resuelven este problema al garantizar que las operaciones sobre ellas se ejecuten de forma secuencial.

Anteriormente discutimos este problema cuando la operación de incremento se desglosaba en tres pasos (leer, incrementar, escribir), pero con las variables atómicas, todo se realiza en una sola operación.

Tipos de variables atómicas en Java

Note
Nota

En general, existen muchas implementaciones atómicas, y no las cubriremos todas aquí, ya que tomaría demasiado tiempo.

Java proporciona varias clases de variables atómicas en el paquete java.util.concurrent.atomic, cada una diseñada para manejar un tipo de dato específico:

  • AtomicInteger: para operaciones atómicas sobre int;
  • AtomicLong: para operaciones atómicas sobre long;
  • AtomicBoolean: para operaciones atómicas sobre boolean;
  • AtomicReference<V>: para operaciones atómicas sobre objetos (tipo genérico).

Métodos

El método get() devuelve el valor actual de una variable. El método set(V newValue) asigna un nuevo valor a la variable. Por otro lado, lazySet(V newValue) es similar a set(), pero puede posponer la actualización del valor, ofreciendo una actualización ordenada en ciertas situaciones.

Main.java

Main.java

copy
123456789101112131415161718192021
package com.example; import java.util.concurrent.atomic.AtomicReference; public class Main { public static void main(String[] args) { AtomicReference<String> atomicString = new AtomicReference<>("Initial Value"); // Using `get()` to retrieve the current value String value = atomicString.get(); System.out.println("Current Value: " + value); // Using `set()` to update the value atomicString.set("New Value"); System.out.println("Value after set(): " + atomicString.get()); // Using `lazySet()` to update the value atomicString.lazySet("Lazy Set Value"); System.out.println("Value after lazySet(): " + atomicString.get()); } }

El método compareAndSet(V expect, V update) actualiza el valor si el valor actual coincide con el valor esperado. Devuelve true si la actualización fue exitosa, y false si el valor actual no coincidió con el valor esperado. En cambio, el método getAndSet(V newValue) establece un nuevo valor y devuelve el valor anterior.

Main.java

Main.java

copy
123456789101112131415161718192021222324
package com.example; import java.util.concurrent.atomic.AtomicReference; public class Main { public static void main(String[] args) { // Initialize an `AtomicReference` with an initial value AtomicReference<String> atomicString = new AtomicReference<>("Initial Value"); // Demonstrate `compareAndSet` boolean success = atomicString.compareAndSet("Initial Value", "Updated Value"); System.out.println("compareAndSet success (expected true): " + success); System.out.println("Current value after compareAndSet: " + atomicString.get()); success = atomicString.compareAndSet("Wrong Value", "Another Update"); System.out.println("compareAndSet success (expected false): " + success); System.out.println("Current value after compareAndSet: " + atomicString.get()); // Demonstrate `getAndSet` String previousValue = atomicString.getAndSet("New Value with getAndSet"); System.out.println("Previous value from getAndSet: " + previousValue); System.out.println("Current value after getAndSet: " + atomicString.get()); } }

Los métodos getAndIncrement() y getAndDecrement() incrementan o decrementan el valor actual en uno y devuelven el valor anterior. Estos métodos se aplican a variables atómicas numéricas como AtomicInteger y AtomicLong. En cambio, los métodos incrementAndGet() y decrementAndGet() también incrementan o decrementan el valor actual en uno pero devuelven el nuevo valor.

Main.java

Main.java

copy
123456789101112131415161718192021222324252627282930
package com.example; import java.util.concurrent.atomic.AtomicInteger; public class Main { public static void main(String[] args) { // Initialize `AtomicInteger` with initial value AtomicInteger atomicInt = new AtomicInteger(10); // Demonstrate `getAndIncrement()` for `AtomicInteger` int oldValueInt = atomicInt.getAndIncrement(); System.out.println("Value before getAndIncrement(): " + oldValueInt); // Should print 10 System.out.println("Value after getAndIncrement(): " + atomicInt.get()); // Should print 11 // Demonstrate `getAndDecrement()` for `AtomicInteger` int oldValueIntDec = atomicInt.getAndDecrement(); System.out.println("Value before getAndDecrement(): " + oldValueIntDec); // Should print 11 System.out.println("Value after getAndDecrement(): " + atomicInt.get()); // Should print 10 // Demonstrate `incrementAndGet()` for `AtomicInteger` int newValueInt = atomicInt.incrementAndGet(); System.out.println("Value after incrementAndGet(): " + newValueInt); // Should print 11 System.out.println("Current value after incrementAndGet(): " + atomicInt.get()); // Should print 11 // Demonstrate `decrementAndGet()` for `AtomicInteger` int newValueIntDec = atomicInt.decrementAndGet(); System.out.println("Value after decrementAndGet(): " + newValueIntDec); // Should print 10 System.out.println("Current value after decrementAndGet(): " + atomicInt.get()); // Should print 10 } }

El método getAndAdd(int delta) suma el valor especificado (delta) al valor actual y devuelve el valor anterior. Este método se utiliza con variables atómicas numéricas. Por otro lado, el método addAndGet(int delta) también suma el valor especificado (delta) al valor actual pero devuelve el nuevo valor.

Main.java

Main.java

copy
123456789101112131415161718192021222324252627282930
package com.example; import java.util.concurrent.atomic.AtomicInteger; public class Main { public static void main(String[] args) { // Initialize `AtomicInteger` with an initial value AtomicInteger atomicInt = new AtomicInteger(50); // Demonstrate `getAndAdd(int delta)` int previousValue = atomicInt.getAndAdd(10); System.out.println("Value before getAndAdd(10): " + previousValue); // Should print 50 System.out.println("Value after getAndAdd(10): " + atomicInt.get()); // Should print 60 // Demonstrate `getAndAdd()` with another delta int previousValue2 = atomicInt.getAndAdd(5); System.out.println("Value before getAndAdd(5): " + previousValue2); // Should print 60 System.out.println("Value after getAndAdd(5): " + atomicInt.get()); // Should print 65 // Demonstrate `addAndGet(int delta)` int newValue = atomicInt.addAndGet(20); System.out.println("Value after addAndGet(20): " + newValue); // Should print 85 System.out.println("Current value after addAndGet(20): " + atomicInt.get()); // Should print 85 // Demonstrate `addAndGet()` with another delta int newValue2 = atomicInt.addAndGet(-15); System.out.println("Value after addAndGet(-15): " + newValue2); // Should print 70 System.out.println("Current value after addAndGet(-15): " + atomicInt.get()); // Should print 70 } }

Ejemplos de uso de variables atómicas

Ejemplo con AtomicInteger (AtomicLong, AtomicBoolean funcionan de la misma manera, por lo que no habrá ejemplos separados para ellos).

Tarea: Implementar un contador que sea incrementado de forma segura por varios hilos.

Main.java

Main.java

copy
1234567891011121314151617181920
package com.example; import java.util.concurrent.atomic.AtomicInteger; public class Main { private AtomicInteger counter = new AtomicInteger(0); public void increment() { int oldValue = counter.getAndIncrement(); System.out.println(Thread.currentThread().getName() + ": Counter was " + oldValue + ", now " + counter.get()); } public static void main(String[] args) { Main atomicCounter = new Main(); for (int i = 0; i < 5; i++) { new Thread(atomicCounter::increment).start(); } } }

Como puede observar, no utilizamos ninguna sincronización aquí, ya que la variable atómica proporciona esta función.

Este ejemplo utiliza AtomicInteger para incrementar el contador de forma segura. El método getAndIncrement() primero devuelve el valor actual de la variable y luego lo incrementa en uno. Esto ocurre de manera atómica, asegurando que la variable se actualice correctamente.

Ejemplo con AtomicReference

Tarea: Proporcionar actualización atómica de una referencia a un objeto.

Main.java

Main.java

copy
123456789101112131415161718192021222324
package com.example; import java.util.concurrent.atomic.AtomicReference; public class Main { // `AtomicReference` to safely update and access a shared `String` value private AtomicReference<String> sharedString = new AtomicReference<>("Initial"); public void updateValue(String newValue) { // Atomically sets the new value and gets the old value String oldValue = sharedString.getAndSet(newValue); // Prints the old and new values, along with the thread name System.out.println(Thread.currentThread().getName() + ": Value was " + oldValue + ", now " + sharedString.get()); } public static void main(String[] args) { Main example = new Main(); // Creates and starts 3 threads, each updating the shared value for (int i = 0; i < 3; i++) { new Thread(() -> example.updateValue("Updated by " + Thread.currentThread().getName())).start(); } } }

AtomicReference se utiliza aquí para actualizar atómicamente el valor de una referencia de cadena. El método getAndSet() establece atómicamente el nuevo valor y devuelve el valor anterior.

Note
Nota

A diferencia de las variables regulares, que requieren sincronización adicional, las variables atómicas utilizan primitivas de bajo nivel para minimizar la sobrecarga y mejorar el rendimiento. Esto las hace especialmente adecuadas para sistemas de alta concurrencia.

1. ¿Cuál es la ventaja de utilizar variables atómicas en la programación multihilo?

2. ¿Qué método de variable atómica proporciona un cambio atómico en el valor si el valor actual es igual al valor esperado?

3. ¿Qué garantiza el método set() en las variables atómicas?

question mark

¿Cuál es la ventaja de utilizar variables atómicas en la programación multihilo?

Select the correct answer

question mark

¿Qué método de variable atómica proporciona un cambio atómico en el valor si el valor actual es igual al valor esperado?

Select the correct answer

question mark

¿Qué garantiza el método set() en las variables atómicas?

Select the correct answer

¿Todo estuvo claro?

¿Cómo podemos mejorarlo?

¡Gracias por tus comentarios!

Sección 3. Capítulo 5
some-alt