"Hallo, Amigo! Weet je nog dat Ellie je vertelde over de problemen die ontstaan ​​wanneer verschillende threads tegelijkertijd toegang proberen te krijgen tot een gedeelde bron, ja?"

"Ja."

'Het punt is, dat is niet alles. Er is nog een klein probleem.'

Zoals je weet heeft een computer geheugen waarin data en commando's (code) worden opgeslagen, evenals een processor die deze commando's uitvoert en met de data werkt. De processor leest gegevens uit het geheugen, wijzigt deze en schrijft deze terug naar het geheugen. Om berekeningen te versnellen, heeft de processor een eigen ingebouwd "snel" geheugen: de cache.

De processor werkt sneller door de meest gebruikte variabelen en geheugengebieden naar de cache te kopiëren. Vervolgens maakt het alle wijzigingen in dit snelle geheugen. En dan kopieert het de gegevens terug naar het "langzame" geheugen. Al die tijd bevat het langzame geheugen de oude (onveranderde!) variabelen.

Dit is waar het probleem zich voordoet. Eén thread verandert een variabele , zoals isCancel of isInterrupted in het bovenstaande voorbeeld, maar een tweede thread ziet deze wijziging niet , omdat deze in het snelle geheugen is gebeurd. Dit is een gevolg van het feit dat de threads geen toegang hebben tot elkaars cache. (Een processor bevat vaak meerdere onafhankelijke cores en de threads kunnen op fysiek verschillende cores draaien.)

Laten we terugdenken aan het voorbeeld van gisteren:

Code Beschrijving
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");
}
}
}
De thread «weet niet» dat de andere threads bestaan.

Bij de methode run wordt de variabele isCancel in de cache van de onderliggende thread geplaatst wanneer deze voor het eerst wordt gebruikt. Deze bewerking is gelijk aan de volgende code:

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

Het aanroepen van de annuleermethode vanuit een andere thread verandert de waarde van isCancel in het normale (trage) geheugen, maar niet in de caches van andere threads.

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

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

"Ho! En hebben ze daar ook een mooie oplossing voor bedacht, zoals met  synchroon ?"

"Je gelooft het niet!"

De eerste gedachte was om de cache uit te schakelen, maar hierdoor werkten programma's vele malen trager. Toen kwam er een andere oplossing.

Het vluchtige zoekwoord was geboren. We plaatsen dit sleutelwoord voor een variabele declaratie om aan te geven dat de waarde ervan niet in de cache moet worden geplaatst. Meer precies, het was niet dat het niet in de cache kon worden gezet, het was gewoon dat het altijd moest worden gelezen van en geschreven naar het normale (trage) geheugen.

Hier leest u hoe u onze oplossing kunt repareren, zodat alles goed werkt:

Code Beschrijving
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");
}
}
}
De vluchtige modifier zorgt ervoor dat een variabele altijd wordt gelezen uit en geschreven naar normaal geheugen dat door alle threads wordt gedeeld.
public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

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

"Dat is het?"

"Dat is het. Eenvoudig en mooi."