"Hello, Amigo! Anda ingat bahawa Ellie memberitahu anda tentang masalah yang timbul apabila beberapa utas cuba mengakses sumber yang dikongsi secara serentak, ya?"

"Ya."

"Perkaranya, bukan itu sahaja. Ada masalah kecil lagi."

Seperti yang anda ketahui, komputer mempunyai memori di mana data dan arahan (kod) disimpan, serta pemproses yang melaksanakan arahan ini dan berfungsi dengan data. Pemproses membaca data daripada ingatan, menukarnya dan menulisnya kembali ke ingatan. Untuk mempercepatkan pengiraan, pemproses mempunyai memori "pantas" terbina dalam sendiri: cache.

Pemproses berjalan lebih pantas dengan menyalin pembolehubah dan kawasan memori yang paling kerap digunakan ke cachenya. Kemudian ia membuat semua perubahan dalam ingatan pantas ini. Dan kemudian ia menyalin data kembali ke memori «perlahan». Selama ini, memori perlahan mengandungi pembolehubah lama (tidak berubah!).

Di sinilah masalah timbul. Satu utas menukar pembolehubah , seperti isCancel atau isInterrupted dalam contoh di atas, tetapi utas kedua «tidak melihat perubahan ini , kerana ia berlaku dalam memori pantas. Ini adalah akibat daripada fakta bahawa benang tidak mempunyai akses kepada cache satu sama lain. (Pemproses selalunya mengandungi beberapa teras bebas dan benang boleh berjalan pada teras yang berbeza secara fizikal.)

Mari kita ingat contoh semalam:

Kod Penerangan
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");
}
}
}
Benang «tidak tahu» bahawa benang lain wujud.

Dalam kaedah larian, pembolehubah isCancel dimasukkan ke dalam cache benang kanak-kanak apabila ia digunakan buat kali pertama. Operasi ini bersamaan dengan kod berikut:

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

Memanggil kaedah batal daripada utas lain akan menukar nilai isCancel dalam memori biasa (perlahan), tetapi tidak dalam cache utas lain.

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

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

"Wah! Dan adakah mereka juga menghasilkan penyelesaian yang cantik untuk ini, seperti dengan  segerak ?"

"Anda tidak akan percaya!"

Pemikiran pertama adalah untuk melumpuhkan cache, tetapi ini menjadikan program berjalan beberapa kali lebih perlahan. Kemudian penyelesaian yang berbeza muncul.

Kata kunci yang tidak menentu telah dilahirkan. Kami meletakkan kata kunci ini sebelum pengisytiharan berubah untuk menunjukkan bahawa nilainya tidak boleh dimasukkan ke dalam cache. Lebih tepat lagi, ia bukan kerana ia tidak boleh dimasukkan ke dalam cache, tetapi ia sentiasa perlu dibaca dan ditulis ke memori biasa (perlahan).

Berikut ialah cara untuk membetulkan penyelesaian kami supaya semuanya berfungsi dengan baik:

Kod Penerangan
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");
}
}
}
Pengubah suai yang tidak menentu menyebabkan pembolehubah sentiasa dibaca dan ditulis ke memori biasa yang dikongsi oleh semua rangkaian.
public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

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

"Itu sahaja?"

"Itu sahaja. Simple dan cantik."