"¡Hola, Amigo! Recuerdas que Ellie te contó sobre los problemas que surgen cuando varios hilos intentan acceder simultáneamente a un recurso compartido, ¿verdad?"

"Sí."

"La cosa es que eso no es todo. Hay otro pequeño problema".

Como sabes, una computadora tiene una memoria donde se almacenan los datos y los comandos (código), así como un procesador que ejecuta estos comandos y trabaja con los datos. El procesador lee los datos de la memoria, los cambia y los vuelve a escribir en la memoria. Para acelerar los cálculos, el procesador tiene su propia memoria "rápida" incorporada: la memoria caché.

El procesador funciona más rápido al copiar las variables y áreas de memoria utilizadas con más frecuencia en su caché. Luego realiza todos los cambios en esta memoria rápida. Y luego copia los datos a la memoria «lenta». Mientras tanto, la memoria lenta contiene las variables antiguas (¡sin cambios!).

Aquí es donde surge el problema. Un subproceso cambia una variable , como isCancel o isInterrupted en el ejemplo anterior, pero un segundo subproceso «no ve este cambio , porque sucedió en la memoria rápida. Esto es consecuencia del hecho de que los subprocesos no tienen acceso a la memoria caché de los demás. (Un procesador a menudo contiene varios núcleos independientes y los subprocesos pueden ejecutarse en núcleos físicamente diferentes).

Recordemos el ejemplo de ayer:

Código Descripción
class Clock implements Runnable
{
private boolean isCancel = false;

public void cancel()
{
this.isCancel = true;
}

public void run()
{
while (!this.isCancel)
{
Thread.sleep(1000);
System.out.println("Tick");
}
}
}
El hilo «no sabe» que los otros hilos existen.

En el método de ejecución, la variable isCancel se coloca en la memoria caché del subproceso secundario cuando se usa por primera vez. Esta operación es equivalente al siguiente código:

public void run()
{
boolean isCancelCached = this.isCancel;
while (!isCancelCached)
{
Thread.sleep(1000);
System.out.println("Tick");
}
}

Llamar al método de cancelación desde otro subproceso cambiará el valor de isCancel en la memoria normal (lenta), pero no en los cachés de otros subprocesos.

public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

Thread.sleep(10000);
clock.cancel();
}

"¡Vaya! ¿Y se les ocurrió una solución hermosa para esto también, como  sincronizada ?"

"¡No lo vas a creer!"

El primer pensamiento fue deshabilitar el caché, pero esto hizo que los programas se ejecutaran varias veces más lento. Entonces surgió una solución diferente.

La palabra clave volátil nació. Ponemos esta palabra clave antes de una declaración de variable para indicar que su valor no se debe poner en el caché. Más precisamente, no era que no pudiera colocarse en el caché, era simplemente que siempre tenía que leerse y escribirse en la memoria normal (lenta).

Aquí le mostramos cómo arreglar nuestra solución para que todo funcione bien:

Código Descripción
class Clock implements Runnable
{
private volatile boolean isCancel = false;

public void cancel()
{
this.isCancel = true;
}

public void run()
{
while (!this.isCancel)
{
Thread.sleep(1000);
System.out.println("Tick");
}
}
}
El modificador volatile hace que una variable siempre se lea y se escriba en la memoria normal compartida por todos los subprocesos.
public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

Thread.sleep(10000);
clock.cancel();
}

"¿Eso es todo?"

"Eso es todo. Simple y hermoso".