2 Volatile¶
Volatile¶
Podemos pensar en las variables volatile como un mecanismo de sincronización ligero, ya que requiere poco código y suponen muy poca sobrecarga en tiempo de ejecución. El inconveniente es que sólo pueden ser usados para resolver un cierto tipo de problemas concretos, un subconjunto de los que pueden ser resueltos con otros tipos de mecanismos de sincronización.
El motivo es que, de las capacidades descritas en el apartado anterior, las variables volatile sólo cumplen con la primera de ellas, la visibilidad, pero no con el resto.
Los hilos que accedan a una variables volatile compartida siempre verán automáticamente su último valor actualizado, por lo que cumple con la propiedad de visibilidad. Sin embargo, al no cumplir con la exclusión mutua, las variables volatile sólo pueden ser usadas como mecanismo de sincronización en aquellos casos en los que no existen restricciones respecto a varias variables a la vez o entre el valor actual de la variable y sus valores futuros.
Para definir una variable como volatile simplemente debemos comenzar su declaración con la palabra reservada volatile
, como en:
En general, para poder utilizar una variable volatile de manera thread-safe, deben cumplirse las siguientes dos condiciones:
- Que la escritura de la variable no dependa de su valor actual. El motivo es que el declarar la variable como
volatile
NO asegura que la operación de lectura del valor actual más el de escritura del nuevo valor se vaya a realizar de forma atómica. Por tanto no deben utilizarse para, por ejemplo, contadores, a no ser que estemos seguros de que sólo va a ser modificada por un único hilo y que el resto de hilos tan sólo va a leer su valor. - Que la variable no participa en invariantes (condición que no varía) con otras variables. El motivo es que si la variable en cuestión, por ejemplo
extremo_inferior
está relacionada con otra, por ejemploextremo_superior
mediante alguna condición que no puede variar,extremo_inferior <= extremo_superior
, definir ambas variables comovolatile
NO asegura que se siga cumpliendo el invariante, al no realizarse la comprobación del invariante y la escritura de forma atómica.
El uso de variables volatile tiene, sin embargo, una serie de ventajas:
- Son mucho más fáciles de usar.
- Como no bloquean los hilos que acceden al recurso, es menos probable que se produzcan problemas de escalabilidad al aumentar el número de hilos.
- Cuando los accesos al recurso es mayoritariamente para lectura y no para escritura, suponen una mejora de rendimiento frente a otros sistemas de sincronización.
Algunos casos típicos en los que podemos usar variables volatile
frente a otros mecanismos de sincronización son los siguientes:
- Con variables que actúan como flags de estado.
- Con variables que sólo se actualizan una única vez.
- Con variables que son actualizadas desde un único hilo, como por ejemplo un hilo en segundo plano que almacena el valor de la temperatura actual y el resto de hilos simplemente leen dicha variable.
Para saber más
Proyecto Volatile¶
En este proyecto vamos a realizar un programa que demuestra un caso extremo en el que es necesario el empleo de la palabra reservada volatile
. El hilo principal define una variable accesible desde otros dos hilos: uno que simplemente detecta cambios en el valor de la variable y otro cuyo cometido es producir dichos cambios.
public class Main {
// Try and remove volatile keyword and run. See what happens.
private static volatile int value = 0;
public static void main(String[] args) {
new ChangeListener().start();
new ChangeMaker().start();
}
static class ChangeListener extends Thread {
@Override
public void run() {
int localValue = value;
// JVM can't update cache value of "value" variable, because is occupied
// permforming the infinite loop. So we have to define the variable as
// volatile
// to ensure it's cache value is updated when updated from another thread.
while (localValue < 5) {
if (localValue != value) {
System.out.printf("Detected new value: %d\n", value);
localValue = value;
}
}
}
}
static class ChangeMaker extends Thread {
@Override
public void run() {
int localValue = value;
while (value < 5) {
System.out.printf("Incrementing value to %d\n", localValue + 1);
value = ++localValue;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
return;
}
}
}
}
}
Si ejecutamos el programa veremos que uno de hilos secundarios detecta los cambios producidos en la variable desde el otro hilo secundario. Sin embargo, si quitamos la palabra reservada volatile
en la definición de la variable, el hilo secundario no será capaz de detectar los cambios que produzca el otro hilo sobre la variable.