Volatilidad y Reordenamiento de Instrucciones en Java

Visibilidad de Memoria

La visibilidad de memoria es un factor crítico en la seguridda de hilos. Considere este ejemplo donde un hilo (t1) permanece en bucle hasta que otro (t2) modifica una variable compartida:

private static int valor;
public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        while (valor == 0) {}
        System.out.println("Hilo t1 finalizado");
    });
    
    Thread t2 = new Thread(() -> {
        Scanner entrada = new Scanner(System.in);
        System.out.println("Ingrese valor:");
        valor = entrada.nextInt();
        System.out.println("valor = " + valor);
    });
    
    t1.start();
    t2.start();
}

Al ejecutarse, t1 no sale del bucle a pesar del cambio en valor. Esto ocurre por optimizaciones de la JVM:

Causa de la Invisibilidad

El bucle involucra dos operaciones:

  1. LOAD: Carga el valor desde memoria al registro del CPU
  2. COMPARE: Compara el valor con 0

La JVM detecta repeticiones innecesarias y optimiza: tras la primera carga, reutiliza el valor en caché en lugar de acceder a memoria, ignorando actualizaciones posteriores.

Solución con volatile

La palabra clave volatile desactiva esta optimización:

private static volatile int valor;

Ahora la JVM garantiza que cada lectura accede directamente a memoria.

Efecto de Operaciones I/O

Si agregamos I/O dentro del bucle, la optimización no ocurre:

while (valor == 0) {
    System.out.println("Ejecutando");
}

Las operaciones I/O consumen más recursos que LOAD y generan cambios de estado, evitando la optimización.

Reordenamiento de Instrucciones

Compiladores y CPUs reordenan instrucciones para mejorar rendimiento, lo que en hilos múltiples puede causar inconsistencias.

Caso Práctico

En un singleton con doble verificación:

private volatile static InstanciaUnica instancia;

public static InstanciaUnica obtenerInstancia() {
    if (instancia == null) {
        synchronized (InstanciaUnica.class) {
            if (instancia == null) {
                instancia = new InstanciaUnica();
            }
        }
    }
    return instancia;
}

Sin volatile, la inicialización podría reordenarse:

  1. Asignar memoria
  2. Asignar referencia (antes de inicializar)
  3. Inicializar objeto

Otros hilos podrían recibir la referencia no inicializada.

Solución

volatile previene reordenamientos durante lecturas/escrituras del campo.

Nota: volatile resuelve visibilidad y reordenamiento, pero no garantiza atomicidad para operaciones compuestas.

Etiquetas: java concurrencia volatile VisibilidadMemoria ReordenamientoInstrucciones

Publicado el 7-27 08:01