เรารู้ว่าการดำเนินการพร้อมกันที่ดำเนินการโดย Java จะต้องเสร็จสิ้นในที่สุดโดย CPU ของเรา ในระหว่างนี้เราได้รวบรวมซอร์สโค้ด Java ลงในไฟล์. class แล้วโหลดแล้วดำเนินการโดยเอ็นจิ้นเครื่องเสมือนจริงที่ตีความว่าเป็นภาษาแอสเซมบลีจากนั้นแปลงเป็นคำแนะนำระบบปฏิบัติการจากนั้นแปลงเป็น 1, 0 และในที่สุด CPU ได้รับการยอมรับและดำเนินการ
เมื่อเราพูดถึงการพร้อมกันของ Java เราไม่สามารถช่วยได้ แต่คิดถึงคำหลักทั่วไปใน Java: ผันผวนและซิงโครไนซ์ ต่อไปเราจะวิเคราะห์พวกเขาจากคำปิดสองคำนี้:
หลักการดำเนินการพื้นฐานของความผันผวน
หลักการการใช้งานและการใช้งานของการซิงโครไนซ์
ระเหย
เมื่อพูดถึงความผันผวนผู้สัมภาษณ์เป็นคำถามที่ชอบถามในการสัมภาษณ์ Java เมื่อเราเห็นมันสิ่งแรกที่เราคิดคือการรักษาทัศนวิสัยระหว่างเธรด มันเป็นซิงโครไนซ์ที่มีน้ำหนักเบาซึ่งในบางกรณีสามารถแทนที่ซิงโครไนซ์
บทบาทของความผันผวน:
สำหรับตัวแปรที่แก้ไขโดยความผันผวนโมเดลหน่วยความจำ Java จะตรวจสอบให้แน่ใจว่าค่าตัวแปรที่เห็นโดยเธรดทั้งหมดนั้นสอดคล้องกัน
ความผันผวนของการทำงานอย่างไร:
เราสามารถกำหนดตัวแปรที่ผันผวนกำหนดค่าไอทีและใช้เครื่องมือเพื่อให้ได้คำแนะนำแอสเซมบลีที่สร้างโดยคอมไพเลอร์ JIT เราจะพบว่าเมื่อเขียนไปยังตัวแปรผันผวนจะมีคำแนะนำเพิ่มเติม: คำสั่งนำหน้าด้วยล็อค:
คำสั่งคำนำหน้าล็อคทำให้สองสิ่งกลับไปที่โปรเซสเซอร์มัลติคอร์:
①เขียนข้อมูลของสายแคชโปรเซสเซอร์ปัจจุบันกลับไปยังหน่วยความจำ
การดำเนินการหน่วยความจำการเขียนกลับจะทำให้ที่อยู่หน่วยความจำแคชข้อมูลในซีพียูอื่นเป็นโมฆะ
เมื่อเรารู้สองจุดข้างต้นมันไม่ยากสำหรับเราที่จะเข้าใจกลไกของตัวแปร Volatie
ภายใต้โปรเซสเซอร์หลายตัวเพื่อให้แน่ใจว่าแคชของโปรเซสเซอร์แต่ละตัวมีความสอดคล้องกันจะมีการใช้โปรโตคอลความสอดคล้องของแคช โปรเซสเซอร์แต่ละตัวสูดดมข้อมูลที่เผยแพร่บนบัสเพื่อตรวจสอบว่าค่าแคชหมดอายุหรือไม่
ซิงโครไนซ์
เมื่อคิดถึงการเกิดขึ้นพร้อมกันแบบมัลติเธรดสิ่งแรกที่ฉันคิดคือซิงโครไนซ์ แปลเป็นการซิงโครไนซ์ เราทุกคนรู้ว่ามันเป็นล็อคเฮฟวี่เวท เมื่อใช้สำหรับวิธีการหรือบล็อกรหัสเมื่อเธรดได้รับล็อคนี้เธรดอื่น ๆ จะตกอยู่ในสถานะที่ถูกระงับซึ่งจะปรากฏในสถานะการนอนหลับใน Java เราทุกคนรู้ว่าการระงับและเวลาทำงานของเธรดจะต้องถูกถ่ายโอนไปยังสถานะเคอร์เนลของระบบปฏิบัติการ (ที่สอดคล้องกับสถานะเคอร์เนลคือสถานะผู้ใช้) ซึ่งเป็นทรัพยากร CPU ที่สิ้นเปลืองโดยเฉพาะอย่างยิ่ง
อย่างไรก็ตามหลังจาก Java SE 1.6 ทีมงานบำรุงรักษา Java ได้ทำการปรับให้เหมาะสมที่สุด (การปรับให้เหมาะสมเหล่านี้มีการพูดคุยกันทีละคน) ดังนั้นจึงไม่ใช่ "หนัก" และล็อคใหม่ซึ่งมีข้อดีในอดีต
พูดคุยเกี่ยวกับการซิงโครไนซ์ในด้านต่อไปนี้:
พื้นฐานของการซิงโครไนซ์เพื่อให้เกิดการซิงโครไนซ์
วิธีการที่ซิงโครไนซ์ล็อค
ล็อคบวกล็อคน้ำหนักเบา (สปินล็อค) ล็อคเฮฟวี่เวท
ล็อคการอัพเกรด
วิธีการใช้งานอะตอมใน Java
①พื้นฐานของการซิงโครไนซ์เพื่อให้เกิดการซิงโครไนซ์:
เราสามารถเห็นการซิงโครไนซ์ในการพัฒนาหรือในซอร์สโค้ด Java เช่น Hashtable, StringBuilder และสถานที่อื่น ๆ มีสองวิธีทั่วไป:
ⅰวิธีการซิงโครไนซ์
วิธีการซิงโครไนซ์จะต้องซิงโครไนซ์ก่อนวิธีการเท่านั้น เมื่อเธรดหนึ่งดำเนินการเธรดอื่น ๆ จะหยุดรอจนกว่าจะปล่อยล็อค การใช้วิธีการสามารถแบ่งออกเป็นสองประเภท: สำหรับวิธีการซิงโครไนซ์ทั่วไปและวิธีการคงที่ ความแตกต่างระหว่างพวกเขาคือวัตถุที่ถูกล็อคนั้นแตกต่างกัน ตำแหน่งที่ล็อคของวิธีการทั่วไปคือวัตถุปัจจุบันและตำแหน่งที่ล็อคของวิธีการคงที่คือวัตถุคลาสของคลาสปัจจุบัน
ⅱ, บล็อกวิธีการซิงโครไนซ์
บล็อกวิธีการซิงโครไนซ์ล็อควัตถุที่กำหนดค่าไว้ในวงเล็บหลังจากซิงโครไนซ์ วัตถุนี้อาจเป็นค่าและตัวแปรหรือวัตถุใด ๆ
②วิธีการที่ซิงโครไนซ์ล็อค:
ในข้อกำหนด JVM คุณสามารถเห็นหลักการการใช้งานของการซิงโครไนซ์ใน JVM JVM ใช้วิธีการซิงโครไนซ์ของวิธีการซิงโครไนซ์และบล็อกรหัสตามการป้อนและออกจากวัตถุการตรวจสอบ บล็อกรหัสถูกนำมาใช้โดยใช้คำแนะนำ MonitorEnter และ Monitorexit วิธีการซิงโครไนซ์ไม่ได้รับเฉพาะในข้อกำหนด JVM อย่างไรก็ตามฉันเชื่อว่าหลักการเฉพาะควรแตกต่างกัน มันไม่มีอะไรมากไปกว่าการรวบรวมซอร์สโค้ด Java ลงในไฟล์คลาสและทำเครื่องหมายวิธีการซิงโครไนซ์ในไฟล์คลาส bytecode วิธีนี้จะถูกซิงโครไนซ์เมื่อเอ็นจิน bytecode ดำเนินการวิธีนี้
③deflection Lock, Lightweight Lock (Spin Lock), Heavyweight Lock:
ก่อนที่จะพูดถึงล็อคเราจำเป็นต้องรู้ส่วนหัวของวัตถุ Java และส่วนหัวของวัตถุ Java:
ล็อคที่ใช้โดยซิงโครไนซ์จะถูกเก็บไว้ในส่วนหัวของวัตถุ Java ส่วนหัวของวัตถุ Java มี 32 บิต/64 บิต (ขึ้นอยู่กับจำนวนบิตของระบบปฏิบัติการ) ความยาวมาร์กเวิร์ดเก็บข้อมูล hashcode และล็อคของวัตถุ มีช่องว่าง 2 บิตใน Markword เพื่อแสดงถึงสถานะของ Lock 00, 01, 10, 11, ตามลำดับแสดงถึงการล็อคน้ำหนักเบาล็อคอคติการล็อคเฮฟวี่เวทและเครื่องหมาย GC
ล็อคบวก: ล็อคบวกเรียกว่าล็อคผิดปกติ จากชื่อเราจะเห็นว่ามันเป็นล็อคที่มีแนวโน้มไปยังเธรดที่แน่นอน
ในการพัฒนาจริงเราพบว่าการเกิดขึ้นพร้อมกันหลายเธรดวิธีการซิงโครไนซ์ส่วนใหญ่ดำเนินการโดยเธรดเดียวกันและความน่าจะเป็นของหลายเธรดที่แข่งขันกันสำหรับวิธีหนึ่งค่อนข้างต่ำ ดังนั้นเพื่อที่จะทำให้เธรดได้รับการล็อคด้วยต้นทุนที่ต่ำกว่า เมื่อเธรดเข้าถึงบล็อกการซิงโครไนซ์และรับล็อค ID เธรดของล็อคอคติจะถูกเก็บไว้ในบันทึกล็อคในเฟรมสแต็กของส่วนหัววัตถุและเธรด ในอนาคตเมื่อเธรดเข้าและออกจากบล็อกการซิงโครไนซ์ไม่จำเป็นต้องดำเนินการ CAS เพื่อล็อคและปลดล็อค มีความจำเป็นเพียงอย่างเดียวที่จะตรวจสอบว่ามีการล็อคอคติที่ชี้ไปที่เครื่องหมายปัจจุบันในส่วนหัวของวัตถุ (ในเครื่องหมายมาร์กเวิร์ดมีบิตล็อคอคติเพื่อระบุว่าวัตถุปัจจุบันรองรับการล็อคอคติเราสามารถใช้พารามิเตอร์ JVM เพื่อตั้งค่าการล็อคอคติ)
เกี่ยวกับการเปิดตัวของล็อคลำเอียงล็อคลำเอียงใช้กลไกในการปล่อยล็อคจนกว่าจะมีการแข่งขันดังนั้นเธรดที่ถือล็อคลำเอียงจะปล่อยล็อคเมื่อเธรดอื่น ๆ พยายามที่จะแข่งขันเพื่อล็อคลำเอียง
หมายเหตุ: ใน Java6, 7, Bias Lock เริ่มต้นโดยค่าเริ่มต้น
ล็อคน้ำหนักเบา:
การล็อคที่มีน้ำหนักเบาคือก่อนที่จะดำเนินการบล็อกการซิงโครไนซ์ JVM จะสร้างพื้นที่สำหรับการจัดเก็บบันทึกล็อคในเฟรมสแต็กของเธรดปัจจุบันและคัดลอกมาร์กเวิร์ดในส่วนหัวของวัตถุลงไป จากนั้นเธรดจะพยายามแทนที่ markword ในส่วนหัวของวัตถุด้วยตัวชี้ไปยังบันทึกล็อค หากประสบความสำเร็จเธรดปัจจุบันจะได้รับการล็อค หากล้มเหลวหมายความว่าเธรดอื่น ๆ จะแข่งขันกันเพื่อล็อคและเธรดปัจจุบันจะหมุนเพื่อรับล็อค
④การอัพเกรดล็อค:
หากเธรดปัจจุบันไม่สามารถลองใช้วิธีข้างต้นเพื่อรับล็อคได้หมายความว่าล็อคปัจจุบันอยู่ในการแข่งขันและล็อคจะถูกอัพเกรดเป็นล็อคเฮฟวี่เวท
ความแตกต่างระหว่างล็อคน้ำหนักเบาและล็อคลำเอียง:
ล็อคน้ำหนักเบาใช้การดำเนินการ CAS เพื่อกำจัด mutexes ที่ใช้ในการซิงโครไนซ์โดยไม่มีการแข่งขันในขณะที่ล็อคลำเอียงจะลบการซิงโครไนซ์ทั้งหมดโดยไม่ต้องแข่งขันโดยไม่มีการแข่งขันและแม้แต่การดำเนินการ CAS ก็ยังไม่เสร็จ!
⑤วิธีการใช้การปฏิบัติการปรมาณูใน Java:
ก่อนที่จะเข้าใจว่า Java ใช้การดำเนินงานของอะตอมอย่างไรเราจำเป็นต้องรู้ว่าโปรเซสเซอร์ใช้การดำเนินการอะตอม: อย่างไร:
โปรเซสเซอร์โดยทั่วไปจะแบ่งออกเป็นสองวิธีในการดำเนินการอะตอม: การล็อคแคชและการล็อคบัสซึ่งการล็อคแคชดีกว่าในขณะที่การล็อคบัสนั้นใช้ทรัพยากรมากขึ้น (เราจะไม่อธิบายมากเกินไปเกี่ยวกับวิธีการล็อคสองวิธีที่นี่ แต่จะมีคำอธิบายโดยละเอียดในระบบปฏิบัติการ)
Java ใช้ (ส่วนใหญ่) Loop CAS เพื่อใช้การดำเนินการอะตอม แต่การใช้ CAS เพื่อใช้การปฏิบัติการปรมาณูจะทำให้เกิดปัญหาคลาสสิกต่อไปนี้:
1) ปัญหา ABA
คลาส AtomicstampedReference มีให้ใน JDK เพื่อแก้ไข (ให้การตรวจสอบการอ้างอิงที่คาดหวังและธงที่คาดหวัง)
2) รอบเวลายาวและค่าใช้จ่ายสูง
ไม่สามารถแก้ปัญหานี้ได้นี่เป็นปัญหาทั่วไปของการไหลเวียน
3) การดำเนินการอะตอมของตัวแปรที่ใช้ร่วมกันเท่านั้นที่สามารถรับประกันได้
Atomicreference มีให้ใน JDK เพื่อแก้ปัญหาวางตัวแปรที่ใช้ร่วมกันหลายตัวในชั้นเรียนสำหรับการดำเนินงาน CAS