"สวัสดี Amigo! คุณจำได้ว่า Ellie บอกคุณเกี่ยวกับปัญหาที่เกิดขึ้นเมื่อหลายเธรดพยายามเข้าถึงทรัพยากรที่ใช้ร่วมกันพร้อมกันใช่ไหม"

"ใช่."

“นั่นไม่ใช่ทั้งหมด มีปัญหาเล็กๆ อีกปัญหาหนึ่ง”

อย่างที่คุณทราบ คอมพิวเตอร์มีหน่วยความจำที่เก็บข้อมูลและคำสั่ง (รหัส) เช่นเดียวกับตัวประมวลผลที่ดำเนินการคำสั่งเหล่านี้และทำงานกับข้อมูล โปรเซสเซอร์อ่านข้อมูลจากหน่วยความจำ เปลี่ยนแปลง และเขียนข้อมูลกลับไปยังหน่วยความจำ เพื่อเพิ่มความเร็วในการคำนวณ โปรเซสเซอร์มีหน่วยความจำ "เร็ว" ในตัว: แคช

โปรเซสเซอร์ทำงานเร็วขึ้นโดยการคัดลอกตัวแปรและพื้นที่หน่วยความจำที่ใช้บ่อยที่สุดไปยังแคช จากนั้นจะทำการเปลี่ยนแปลงทั้งหมดในหน่วยความจำที่รวดเร็วนี้ จากนั้นจะคัดลอกข้อมูลกลับไปยังหน่วยความจำ «ช้า» ทั้งหมดนี้ หน่วยความจำที่ช้ามีตัวแปรเก่า (ไม่เปลี่ยนแปลง!)

นี่คือจุดที่ปัญหาเกิดขึ้น เธรดหนึ่งเปลี่ยนตัวแปรเช่น isCancel หรือ isInterrupted ในตัวอย่างข้างต้น แต่เธรดที่สอง «ไม่เห็นการเปลี่ยนแปลงนี้เนื่องจากเกิดขึ้นในหน่วยความจำแบบเร็ว นี่เป็นผลมาจากข้อเท็จจริงที่ว่าเธรดไม่มีสิทธิ์เข้าถึงแคชของกันและกัน (โปรเซสเซอร์มักประกอบด้วยคอร์อิสระหลายคอร์ และเธรดสามารถทำงานบนคอร์ที่แตกต่างกันได้)

จำตัวอย่างเมื่อวาน:

รหัส คำอธิบาย
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");
}
}
}
เธรด «ไม่รู้» ว่ามีเธรดอื่นอยู่

ในวิธีการเรียกใช้ ตัวแปร isCancel จะถูกใส่ลงในแคชของเธรดลูกเมื่อถูกใช้เป็นครั้งแรก การดำเนินการนี้เทียบเท่ากับรหัสต่อไปนี้:

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

การเรียก วิธี การยกเลิกจากเธรดอื่นจะเปลี่ยนค่าของisCancelในหน่วยความจำปกติ (ช้า) แต่ไม่อยู่ในแคชของเธรดอื่น

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

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

"ว้าว! และพวกเขาคิดวิธีแก้ปัญหาที่สวยงามสำหรับสิ่งนี้ด้วย เช่นซิง  โครไนซ์ ?"

"คุณจะไม่เชื่อ!"

ความคิดแรกคือการปิดใช้งานแคช แต่สิ่งนี้ทำให้โปรแกรมทำงานช้าลงหลายเท่า จากนั้นทางออกอื่นก็เกิดขึ้น

เกิดคำหลักที่ผันผวน เราใส่คีย์เวิร์ดนี้ไว้หน้าการประกาศตัวแปรเพื่อระบุว่าค่าของคีย์เวิร์ดไม่ต้องใส่ในแคช แม่นยำกว่านั้น ไม่ใช่ว่าไม่สามารถใส่ลงในแคชได้ แต่เป็นเพียงว่าต้องอ่านและเขียนจากหน่วยความจำปกติ (ช้า) เสมอ

ต่อไปนี้เป็นวิธีแก้ไขปัญหาของเราเพื่อให้ทุกอย่างทำงานได้ดี:

รหัส คำอธิบาย
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");
}
}
}
โมดิฟายเออร์ระเหยทำให้ตัวแปรถูกอ่านและเขียนไปยังหน่วยความจำปกติที่ใช้ร่วมกันโดยเธรดทั้งหมดเสมอ
public static void main(String[] args)
{
Clock clock = new Clock();
Thread clockThread = new Thread(clock);
clockThread.start();

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

"แค่นั้นแหละ?"

"นั่นแหล่ะ เรียบง่ายและสวยงาม"