"Olá, amigo! Você se lembra que Ellie lhe contou sobre os problemas que surgem quando vários threads tentam acessar simultaneamente um recurso compartilhado, certo?"

"Sim."

"A questão é que isso não é tudo. Há outro pequeno problema."

Como você sabe, um computador possui uma memória onde são armazenados dados e comandos (códigos), além de um processador que executa esses comandos e trabalha com os dados. O processador lê os dados da memória, os altera e os escreve de volta na memória. Para acelerar os cálculos, o processador possui sua própria memória "rápida" integrada: o cache.

O processador roda mais rápido copiando as variáveis ​​e áreas de memória usadas com mais frequência para seu cache. Em seguida, ele faz todas as alterações nessa memória rápida. E então copia os dados de volta para a memória «lenta». Durante todo esse tempo, a memória lenta contém as variáveis ​​antigas (inalteradas!).

É aqui que surge o problema. Um thread altera uma variável , como isCancel ou isInterrupted no exemplo acima, mas um segundo thread «não vê essa alteração , porque aconteceu na memória rápida. Isso é consequência do fato de que os threads não têm acesso ao cache um do outro. (Um processador geralmente contém vários núcleos independentes e os threads podem ser executados em núcleos fisicamente diferentes.)

Vamos relembrar o exemplo de ontem:

Código Descrição
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");
}
}
}
O thread «não sabe» que os outros threads existem.

No método run, a variável isCancel é colocada no cache do thread filho quando é usada pela primeira vez. Esta operação é equivalente ao seguinte código:

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

Chamar o método cancel de outro thread alterará o valor de isCancel na memória normal (lenta), mas não nos caches de outros threads.

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

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

"Uau! E eles criaram uma bela solução para isso também, como  sincronizado ?"

"Você não vai acreditar!"

A primeira ideia foi desabilitar o cache, mas isso fez com que os programas rodassem várias vezes mais devagar. Então surgiu uma solução diferente.

A palavra-chave volátil nasceu. Colocamos esta palavra-chave antes de uma declaração de variável para indicar que seu valor não deve ser colocado no cache. Mais precisamente, não era que não pudesse ser colocado no cache, era simplesmente que sempre tinha que ser lido e gravado na memória normal (lenta).

Veja como corrigir nossa solução para que tudo funcione bem:

Código Descrição
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");
}
}
}
O modificador volátil faz com que uma variável seja sempre lida e gravada na memória normal compartilhada por todos os threads.
public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

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

"É isso?"

"É isso. Simples e bonito."