"Helló, Amigo! Emlékszel, hogy Ellie mesélt neked azokról a problémákról, amelyek akkor merülnek fel, amikor több szál egyszerre próbál hozzáférni egy megosztott erőforráshoz, igaz?"

"Igen."

"Az a helyzet, hogy ez még nem minden. Van még egy apró probléma."

Mint ismeretes, a számítógép rendelkezik memóriával, ahol az adatok és a parancsok (kód) vannak tárolva, valamint egy processzor, amely végrehajtja ezeket a parancsokat és dolgozik az adatokkal. A processzor beolvassa az adatokat a memóriából, megváltoztatja és visszaírja a memóriába. A számítások felgyorsítása érdekében a processzornak saját beépített "gyors" memóriája van: a gyorsítótár.

A processzor gyorsabban fut, ha a leggyakrabban használt változókat és memóriaterületeket a gyorsítótárába másolja. Ezután minden változtatást végrehajt ebben a gyors memóriában. Ezután visszamásolja az adatokat a „lassú” memóriába. Mindeközben a lassú memória a régi (változatlan!) változókat tartalmazza.

Itt merül fel a probléma. Az egyik szál megváltoztatja a változót , például a fenti példában az isCancel vagy az isInterrupted értéket, de a második szál «nem látja ezt a változást , mert a gyorsmemóriában történt. Ez annak a következménye, hogy a szálak nem férnek hozzá egymás gyorsítótárához. (Egy processzor gyakran több független magot tartalmaz, és a szálak fizikailag különböző magokon futhatnak.)

Idézzük fel a tegnapi példát:

Kód Leírás
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");
}
}
}
A szál «nem tudja», hogy a többi szál létezik.

A futtatási metódusban az isCancel változó a gyermekszál gyorsítótárába kerül az első használatkor. Ez a művelet egyenértékű a következő kóddal:

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

A cancel metódus másik szálból való meghívása megváltoztatja az isCancel értékét a normál (lassú) memóriában, de nem a többi szál gyorsítótárában.

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

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

"Hűha! És erre is kitaláltak valami szép megoldást, mint a  szinkronizáltnál ?"

– Nem fogod elhinni!

Az első gondolat a gyorsítótár letiltása volt, de ettől a programok többször is lelassultak. Aztán más megoldás született.

Megszületett az illékony kulcsszó. Ezt a kulcsszót egy változódeklaráció elé tesszük, jelezve, hogy értéke nem kerülhet a gyorsítótárba. Pontosabban nem arról volt szó, hogy nem lehetett a cache-be tenni, hanem egyszerűen mindig a normál (lassú) memóriából kellett kiolvasni és odaírni.

A következőképpen javíthatjuk ki megoldásunkat, hogy minden jól működjön:

Kód Leírás
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");
}
}
}
Az illékony módosító hatására egy változó mindig kiolvasható és az összes szál által megosztott normál memóriába írható.
public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

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

"Ez az?"

"Ennyi. Egyszerű és gyönyörű."