สถาปัตยกรรมฮาร์ดแวร์หน่วยความจำ
สถาปัตยกรรมฮาร์ดแวร์หน่วยความจำสมัยใหม่แตกต่างจากโมเดลหน่วยความจำภายในของ Java ดังนั้น คุณต้องเข้าใจสถาปัตยกรรมฮาร์ดแวร์เพื่อที่จะรู้ว่าโมเดล Java นั้นทำงานอย่างไร ส่วนนี้อธิบายถึงสถาปัตยกรรมฮาร์ดแวร์หน่วยความจำทั่วไป และส่วนถัดไปจะอธิบายวิธีที่ Java ทำงานร่วมกับมัน
นี่คือไดอะแกรมอย่างง่ายของสถาปัตยกรรมฮาร์ดแวร์ของคอมพิวเตอร์สมัยใหม่:
ในโลกสมัยใหม่ คอมพิวเตอร์มีโปรเซสเซอร์ตั้งแต่ 2 ตัวขึ้นไป และนี่เป็นบรรทัดฐานอยู่แล้ว โปรเซสเซอร์เหล่านี้บางตัวอาจมีหลายคอร์ บนคอมพิวเตอร์ดังกล่าว เป็นไปได้ที่จะเรียกใช้หลายเธรดในเวลาเดียวกัน แกนประมวลผลแต่ละตัวสามารถดำเนินการหนึ่งเธรดในเวลาใดก็ได้ ซึ่งหมายความว่าแอ็พพลิเคชัน Java ใดๆ เป็นแบบมัลติเธรดที่มีความสำคัญ และภายในโปรแกรมของคุณ หนึ่งเธรดต่อแกนประมวลผลสามารถทำงานพร้อมกันได้
แกนประมวลผลประกอบด้วยชุดของการลงทะเบียนที่อยู่ในหน่วยความจำ (ภายในแกน) มันดำเนินการกับข้อมูลการลงทะเบียนได้เร็วกว่าข้อมูลที่อยู่ในหน่วยความจำหลัก (RAM) ของคอมพิวเตอร์ นี่เป็นเพราะโปรเซสเซอร์สามารถเข้าถึงการลงทะเบียนเหล่านี้ได้เร็วกว่ามาก
ซีพียูแต่ละตัวสามารถมีชั้นแคชของตัวเองได้ โปรเซสเซอร์ที่ทันสมัยส่วนใหญ่มี โปรเซสเซอร์สามารถเข้าถึงแคชได้เร็วกว่าหน่วยความจำหลัก แต่ไม่เร็วเท่ากับการลงทะเบียนภายใน ค่าของความเร็วในการเข้าถึงแคชมีค่าประมาณระหว่างความเร็วการเข้าถึงของหน่วยความจำหลักและรีจิสเตอร์ภายใน
ยิ่งกว่านั้นโปรเซสเซอร์ยังมีที่สำหรับแคชหลายระดับ แต่สิ่งนี้ไม่สำคัญนักที่จะต้องรู้เพื่อที่จะเข้าใจว่าโมเดลหน่วยความจำ Java โต้ตอบกับหน่วยความจำฮาร์ดแวร์อย่างไร สิ่งสำคัญคือต้องรู้ว่าโปรเซสเซอร์อาจมีแคชในระดับหนึ่ง
คอมพิวเตอร์ทุกเครื่องก็มี RAM (พื้นที่หน่วยความจำหลัก) ในลักษณะเดียวกัน คอร์ทั้งหมดสามารถเข้าถึงหน่วยความจำหลักได้ พื้นที่หน่วยความจำหลักมักจะใหญ่กว่าหน่วยความจำแคชของแกนประมวลผล
ในขณะที่โปรเซสเซอร์ต้องการเข้าถึงหน่วยความจำหลัก โปรเซสเซอร์จะอ่านส่วนหนึ่งของโปรเซสเซอร์ลงในหน่วยความจำแคช นอกจากนี้ยังสามารถอ่านข้อมูลบางอย่างจากแคชไปยังรีจิสเตอร์ภายในและดำเนินการกับข้อมูลเหล่านั้นได้ เมื่อ CPU จำเป็นต้องเขียนผลลัพธ์กลับไปยังหน่วยความจำหลัก มันจะล้างข้อมูลจากการลงทะเบียนภายในไปยังแคช และในบางจุดไปยังหน่วยความจำหลัก
ข้อมูลที่จัดเก็บไว้ในแคชจะถูกล้างกลับไปยังหน่วยความจำหลักตามปกติ เมื่อโปรเซสเซอร์ต้องการเก็บสิ่งอื่นไว้ในแคช แคชมีความสามารถในการล้างหน่วยความจำและเขียนข้อมูลในเวลาเดียวกัน โปรเซสเซอร์ไม่จำเป็นต้องอ่านหรือเขียนแคชทั้งหมดทุกครั้งระหว่างการอัพเดต โดยปกติแคชจะได้รับการอัปเดตในหน่วยความจำขนาดเล็กซึ่งเรียกว่า "แคชไลน์" "บรรทัดแคช" อย่างน้อยหนึ่งรายการอาจถูกอ่านในหน่วยความจำแคช และอาจมีการล้างบรรทัดแคชหนึ่งรายการขึ้นไปกลับไปยังหน่วยความจำหลัก
รวมโมเดลหน่วยความจำ Java และสถาปัตยกรรมฮาร์ดแวร์หน่วยความจำ
ดังที่กล่าวไปแล้ว โมเดลหน่วยความจำ Java และสถาปัตยกรรมฮาร์ดแวร์หน่วยความจำนั้นแตกต่างกัน สถาปัตยกรรมฮาร์ดแวร์ไม่ได้แยกความแตกต่างระหว่างเธรดสแต็กและฮีป บนฮาร์ดแวร์ เธรดสแต็กและ HEAP (ฮีป) จะอยู่ในหน่วยความจำหลัก
บางส่วนของสแต็กและเธรดฮีปอาจมีอยู่ในแคชและรีจิสเตอร์ภายในของ CPU ในบางครั้ง สิ่งนี้แสดงในแผนภาพ:
เมื่อวัตถุและตัวแปรสามารถจัดเก็บในพื้นที่ต่างๆ ของหน่วยความจำของคอมพิวเตอร์ ปัญหาบางอย่างอาจเกิดขึ้นได้ นี่คือสองรายการหลัก:
- การมองเห็นการเปลี่ยนแปลงที่เธรดทำกับตัวแปรที่ใช้ร่วมกัน
- สภาพการแข่งขันเมื่ออ่าน ตรวจสอบ และเขียนตัวแปรที่ใช้ร่วมกัน
ประเด็นทั้งสองนี้จะอธิบายไว้ด้านล่าง
การมองเห็นของวัตถุที่ใช้ร่วมกัน
หากเธรดตั้งแต่สองเธรดขึ้นไปใช้ออบเจกต์ร่วมกันโดยไม่ได้ใช้การประกาศหรือซิงโครไนซ์ที่เหมาะสม การเปลี่ยนแปลงออบเจกต์ที่ใช้ร่วมกันที่ทำโดยเธรดหนึ่งอาจไม่สามารถมองเห็นได้ในเธรดอื่นๆ
ลองนึกภาพว่าวัตถุที่ใช้ร่วมกันถูกจัดเก็บไว้ในหน่วยความจำหลัก เธรดที่ทำงานบน CPU จะอ่านวัตถุที่ใช้ร่วมกันลงในแคชของ CPU เดียวกัน ที่นั่นเขาทำการเปลี่ยนแปลงกับวัตถุ เธรดที่ทำงานบน CPU อื่นจนกว่าแคชของ CPU จะถูกล้างไปยังหน่วยความจำหลัก ดังนั้น แต่ละเธรดจะได้รับสำเนาของตัวเองของวัตถุที่ใช้ร่วมกัน แต่ละสำเนาจะอยู่ในแคช CPU แยกต่างหาก
แผนภาพต่อไปนี้แสดงโครงร่างของสถานการณ์นี้ เธรดหนึ่งที่ทำงานบน CPU ด้านซ้ายจะคัดลอกวัตถุที่ใช้ร่วมกันลงในแคชและเปลี่ยนค่าของการนับเป็น 2 การเปลี่ยนแปลงนี้มองไม่เห็นสำหรับเธรดอื่นๆ ที่ทำงานบน CPU ด้านขวา เนื่องจากการอัปเดตที่จะนับยังไม่ถูกล้างกลับไปยังหน่วยความจำหลัก
เพื่อแก้ปัญหานี้ คุณสามารถใช้คำหลักที่เปลี่ยนแปลงได้เมื่อประกาศตัวแปร สามารถมั่นใจได้ว่าตัวแปรที่กำหนดจะถูกอ่านโดยตรงจากหน่วยความจำหลัก และจะถูกเขียนกลับไปยังหน่วยความจำหลักเสมอเมื่อมีการอัพเดท
สภาพการแข่งขัน
ถ้าเธรดตั้งแต่สองเธรดขึ้นไปใช้ออบเจกต์เดียวกันร่วมกัน และเธรดอัพเดตตัวแปรมากกว่าหนึ่งเธรดในอ็อบเจกต์ที่ใช้ร่วมกันนั้น อาจเกิดสภาวะการแย่งชิง
ลองนึกภาพว่าเธรด A อ่านตัวแปรนับของออบเจกต์ที่ใช้ร่วมกันลงในแคชของตัวประมวลผล ลองนึกภาพว่าเธรด B ทำสิ่งเดียวกัน แต่ในแคชของโปรเซสเซอร์อื่น ตอนนี้เธรด A เพิ่ม 1 ให้กับค่าของการนับ และเธรด B ก็ทำเช่นเดียวกัน ตอนนี้ตัวแปรเพิ่มขึ้นสองครั้ง - แยกจากกันโดย +1 ในแคชของโปรเซสเซอร์แต่ละตัว
หากเพิ่มเหล่านี้ตามลำดับ ตัวแปรนับจะเพิ่มเป็นสองเท่าและเขียนกลับไปยังหน่วยความจำหลัก (ค่าเดิม + 2)
อย่างไรก็ตาม มีการเพิ่มทีละสองครั้งพร้อมกันโดยไม่มีการซิงโครไนซ์ที่เหมาะสม ไม่ว่าเธรดใด (A หรือ B) จะเขียนการนับเวอร์ชันที่อัปเดตลงในหน่วยความจำหลัก ค่าใหม่จะมากกว่าค่าเดิมเพียง 1 ค่าเท่านั้น แม้จะเพิ่มขึ้นสองครั้งก็ตาม
แผนผังนี้แสดงการเกิดปัญหาสภาพการแข่งขันที่อธิบายไว้ข้างต้น:
เพื่อแก้ปัญหานี้ คุณสามารถใช้ Java synchronized block บล็อกที่ซิงโครไนซ์ช่วยให้มั่นใจได้ว่ามีเพียงหนึ่งเธรดเท่านั้นที่สามารถเข้าสู่ส่วนสำคัญของโค้ดที่กำหนดในเวลาใดก็ได้
บล็อกที่ซิงโครไนซ์ยังรับประกันว่าตัวแปรทั้งหมดที่เข้าถึงภายในบล็อกที่ซิงโครไนซ์จะถูกอ่านจากหน่วยความจำหลัก และเมื่อเธรดออกจากบล็อกที่ซิงโครไนซ์ ตัวแปรที่อัปเดตทั้งหมดจะถูกล้างกลับไปยังหน่วยความจำหลัก โดยไม่คำนึงว่าตัวแปรนั้นถูกประกาศเปลี่ยนแปลงหรือไม่ก็ตาม
GO TO FULL VERSION