ในการประมวลผลพร้อมกันของเธรด Java ขณะนี้มีความสับสนอย่างมากเกี่ยวกับการใช้คีย์เวิร์ดระเหย คิดว่าการใช้คีย์เวิร์ดนี้ ทุกอย่างจะเรียบร้อยดีเมื่อทำการประมวลผลพร้อมกันแบบมัลติเธรด
ภาษา Java รองรับการทำงานแบบมัลติเธรด เพื่อแก้ไขปัญหาการทำงานพร้อมกันของเธรด จึงมีการใช้กลไกการบล็อกแบบซิงโครไนซ์และคำสำคัญที่เปลี่ยนแปลงได้ภายในภาษา
ซิงโครไนซ์
ทุกคนคุ้นเคยกับบล็อกที่ซิงโครไนซ์ซึ่งใช้งานผ่านคีย์เวิร์ดที่ซิงโครไนซ์ ด้วยการเพิ่มคำสั่งซิงโครไนซ์และบล็อก เมื่อเข้าถึงโดยหลายเธรด เธรดเดียวเท่านั้นที่สามารถใช้วิธีการแก้ไขที่ซิงโครไนซ์หรือบล็อคโค้ดในเวลาเดียวกัน
ระเหย
สำหรับตัวแปรที่ถูกแก้ไขโดยมีความผันผวน ทุกครั้งที่เธรดใช้ตัวแปร มันจะอ่านค่าที่แก้ไขของตัวแปร สารระเหยสามารถนำไปใช้ในทางที่ผิดได้อย่างง่ายดายในการดำเนินการของอะตอม
ลองดูตัวอย่างด้านล่าง เราใช้ตัวนับ ทุกครั้งที่เริ่มเธรด วิธีการตัวนับ inc จะถูกเรียกเพื่อเพิ่มตัวนับ
สภาพแวดล้อมการดำเนินการ - เวอร์ชัน jdk: jdk1.6.0_31, หน่วยความจำ: 3G cpu: x86 2.4G
คัดลอกรหัสรหัส ดังต่อไปนี้:
เคาน์เตอร์คลาสสาธารณะ {
จำนวน int คงที่สาธารณะ = 0;
โมฆะคงที่สาธารณะ inc () {
//ดีเลย์ 1 มิลลิวินาทีที่นี่เพื่อทำให้ผลลัพธ์ชัดเจน
พยายาม {
เธรด.สลีป(1);
} จับ (InterruptedException e) {
-
นับ++;
-
โมฆะคงที่สาธารณะ main (String [] args) {
//เริ่ม 1,000 เธรดพร้อมกันเพื่อทำการคำนวณ i++ และดูผลลัพธ์จริง
สำหรับ (int i = 0; i < 1,000; i++) {
เธรดใหม่ (เรียกใช้ใหม่ () {
@แทนที่
โมฆะสาธารณะวิ่ง () {
Counter.inc();
-
}).เริ่ม();
-
//ค่าที่นี่อาจแตกต่างกันในแต่ละครั้งที่รัน ซึ่งอาจเป็น 1,000
System.out.println("ผลการรัน: Counter.count=" + Counter.count);
-
-
ผลการดำเนินการ: Counter.count=995
ผลการดำเนินการจริงอาจแตกต่างกันทุกครั้ง ผลลัพธ์ของเครื่องนี้คือ: ผลการทำงาน: Counter.count=995 จะเห็นได้ว่าในสภาพแวดล้อมแบบมัลติเธรด Counter.count ไม่ได้คาดหวังผลลัพธ์ที่จะเป็น 1000
หลายคนคิดว่านี่เป็นปัญหาการทำงานพร้อมกันแบบหลายเธรด คุณจะต้องเพิ่มความผันผวนก่อนที่จะนับตัวแปรเพื่อหลีกเลี่ยงปัญหานี้ จากนั้นเราจะแก้ไขโค้ดเพื่อดูว่าผลลัพธ์ตรงตามความคาดหวังของเราหรือไม่
คัดลอกรหัสรหัส ดังต่อไปนี้:
เคาน์เตอร์คลาสสาธารณะ {
จำนวน int คงที่ที่ผันผวนสาธารณะ = 0;
โมฆะคงที่สาธารณะ inc () {
//ดีเลย์ 1 มิลลิวินาทีที่นี่เพื่อทำให้ผลลัพธ์ชัดเจน
พยายาม {
เธรด.สลีป(1);
} จับ (InterruptedException e) {
-
นับ++;
-
โมฆะคงที่สาธารณะ main (String [] args) {
//เริ่ม 1,000 เธรดพร้อมกันเพื่อทำการคำนวณ i++ และดูผลลัพธ์จริง
สำหรับ (int i = 0; i < 1,000; i++) {
เธรดใหม่ (เรียกใช้ใหม่ () {
@แทนที่
โมฆะสาธารณะวิ่ง () {
Counter.inc();
-
}).เริ่ม();
-
//ค่าที่นี่อาจแตกต่างกันในแต่ละครั้งที่รัน ซึ่งอาจเป็น 1,000
System.out.println("ผลการรัน: Counter.count=" + Counter.count);
-
-
ผลการดำเนินการ: Counter.count=992
ผลการวิ่งยังไม่ 1,000 อย่างที่เราคาดไว้ มาวิเคราะห์เหตุผลด้านล่างกัน
ในบทความ Java Garbage Collection มีการอธิบายการจัดสรรหน่วยความจำระหว่างรันไทม์ JVM หนึ่งในพื้นที่หน่วยความจำคือสแต็กเครื่องเสมือน jvm แต่ละเธรดมีสแต็กเธรดเมื่อรันอยู่ เมื่อเธรดเข้าถึงค่าของออบเจ็กต์ อันดับแรกจะค้นหาค่าของตัวแปรที่สอดคล้องกับหน่วยความจำฮีปผ่านการอ้างอิงออบเจ็กต์ จากนั้นโหลดค่าเฉพาะของตัวแปรหน่วยความจำฮีปลงในหน่วยความจำภายในของเธรดเพื่อสร้างสำเนาของ ตัวแปร หลังจากนั้นเธรดจะไม่เกี่ยวข้องกับค่าตัวแปรของวัตถุในหน่วยความจำฮีป แต่จะแก้ไขค่าของตัวแปรคัดลอกโดยตรง ณ เวลาหนึ่งหลังจากการแก้ไข (ก่อนที่เธรดจะออก) ค่าของสำเนาตัวแปรเธรดจะถูกเขียนกลับไปยังตัวแปรของวัตถุในฮีปโดยอัตโนมัติ ด้วยวิธีนี้ ค่าของวัตถุในฮีปจะเปลี่ยนไป รูปภาพด้านล่าง
อธิบายปฏิสัมพันธ์นี้
อ่านและโหลดตัวแปรคัดลอกจากหน่วยความจำหลักไปยังหน่วยความจำทำงานปัจจุบัน
ใช้และกำหนดโค้ดดำเนินการและเปลี่ยนค่าตัวแปรที่ใช้ร่วมกัน
จัดเก็บและเขียนรีเฟรชเนื้อหาที่เกี่ยวข้องกับหน่วยความจำหลักด้วยข้อมูลหน่วยความจำที่ใช้งาน
โดยที่การใช้และการกำหนดสามารถปรากฏได้หลายครั้ง
อย่างไรก็ตาม การดำเนินการเหล่านี้ไม่ใช่อะตอมมิก นั่นคือ หลังจากโหลดการอ่านแล้ว หากมีการแก้ไขตัวแปรการนับหน่วยความจำหลัก ค่าในหน่วยความจำการทำงานของเธรดจะไม่เปลี่ยนแปลงเนื่องจากถูกโหลดแล้ว ดังนั้นผลลัพธ์ที่คำนวณได้จะเป็นไปตามที่คาดไว้ เหมือนกัน
สำหรับตัวแปรที่แก้ไขแบบระเหย เครื่องเสมือน JVM จะรับรองว่าค่าที่โหลดจากหน่วยความจำหลักไปยังหน่วยความจำการทำงานของเธรดนั้นเป็นค่าล่าสุดเท่านั้น
ตัวอย่างเช่น หากเธรด 1 และเธรด 2 พบว่าค่าการนับในหน่วยความจำหลักเป็นทั้ง 5 ระหว่างการดำเนินการอ่านและโหลด ทั้งสองจะโหลดค่าล่าสุด
หลังจากแก้ไขจำนวนฮีปของเธรด 1 แล้ว จำนวนฮีปนั้นจะถูกเขียนลงในหน่วยความจำหลัก และตัวแปรการนับในหน่วยความจำหลักจะกลายเป็น 6
เนื่องจากเธรด 2 ได้ดำเนินการอ่านและโหลดแล้ว หลังจากดำเนินการแล้ว เธรดจะอัปเดตค่าตัวแปรจำนวนหน่วยความจำหลักเป็น 6 ด้วย
ด้วยเหตุนี้ หลังจากที่สองเธรดได้รับการแก้ไขทันเวลาโดยใช้คีย์เวิร์ดที่เปลี่ยนแปลงได้ ก็จะยังคงมีการทำงานพร้อมกัน