"Hello, Amigo! Naaalala mo na sinabi sa iyo ni Ellie ang tungkol sa mga problemang lumalabas kapag sinubukan ng ilang thread na sabay-sabay na i-access ang isang shared resource, oo?"

"Oo."

"The thing is, hindi lang yun. May isa pang maliit na problema."

Tulad ng alam mo, ang isang computer ay may memorya kung saan naka-imbak ang data at mga utos (code), pati na rin ang isang processor na nagsasagawa ng mga utos na ito at gumagana sa data. Ang processor ay nagbabasa ng data mula sa memorya, binabago ito, at isinusulat ito pabalik sa memorya. Upang mapabilis ang mga pagkalkula, ang processor ay may sariling built-in na "mabilis" na memorya: ang cache.

Ang processor ay tumatakbo nang mas mabilis sa pamamagitan ng pagkopya ng pinakamadalas na ginagamit na mga variable at bahagi ng memorya sa cache nito. Pagkatapos ay ginagawa nito ang lahat ng mga pagbabago sa mabilis na memorya na ito. At pagkatapos ay kinokopya nito ang data pabalik sa "mabagal" na memorya. Sa lahat ng ito, ang mabagal na memorya ay naglalaman ng mga lumang (hindi nagbabago!) na mga variable.

Dito lumalabas ang problema. Binabago ng isang thread ang isang variable , tulad ng isCancel o isInterrupted sa halimbawa sa itaas, ngunit ang pangalawang thread «ay hindi nakikita ang pagbabagong ito , dahil nangyari ito sa mabilis na memorya. Ito ay bunga ng katotohanan na ang mga thread ay walang access sa cache ng bawat isa. (Ang isang processor ay kadalasang naglalaman ng ilang independiyenteng mga core at ang mga thread ay maaaring tumakbo sa pisikal na magkakaibang mga core.)

Alalahanin natin ang halimbawa kahapon:

Code Paglalarawan
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");
}
}
}
Ang thread na «hindi alam» na ang iba pang mga thread ay umiiral.

Sa paraan ng pagtakbo, ang isCancel variable ay inilalagay sa cache ng thread ng bata kapag ginamit ito sa unang pagkakataon. Ang operasyong ito ay katumbas ng sumusunod na code:

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

Ang pagtawag sa paraan ng pagkansela mula sa isa pang thread ay magbabago sa halaga ng isCancel sa normal (mabagal) na memorya, ngunit hindi sa mga cache ng ibang mga thread.

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

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

"Whoa! At nakaisip din ba sila ng magandang solusyon para dito, tulad ng naka  -synchronize ?"

"Hindi ka maniniwala!"

Ang unang naisip ay huwag paganahin ang cache, ngunit ginawa nito ang mga programa na tumakbo nang maraming beses na mas mabagal. Pagkatapos ay lumitaw ang ibang solusyon.

Ang pabagu-bagong keyword ay ipinanganak. Inilalagay namin ang keyword na ito bago ang isang variable na deklarasyon upang ipahiwatig na ang halaga nito ay hindi dapat ilagay sa cache. Mas tiyak, ito ay hindi na hindi ito maaaring ilagay sa cache, ito ay lamang na ito ay palaging kailangang basahin mula sa at isulat sa normal (mabagal) memorya.

Narito kung paano ayusin ang aming solusyon para gumana nang maayos ang lahat:

Code Paglalarawan
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");
}
}
}
Ang pabagu-bago ng isip na modifier ay nagiging sanhi ng isang variable na palaging basahin at isulat sa normal na memorya na ibinahagi ng lahat ng mga thread.
public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

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

"Ayan yun?"

"Ayan. Simple at maganda."