ในรหัสโปรแกรม C เราสามารถใช้ Mutex Lock ที่จัดทำโดยระบบปฏิบัติการเพื่อให้ได้การเข้าถึง Mutex ไปยังบล็อกซิงโครนัสและการปิดกั้นเธรดและการปลุก อย่างไรก็ตามใน Java นอกเหนือจากการจัดหา Lockapi แล้วยังมีการให้คำหลักที่ซิงโครไนซ์ในระดับไวยากรณ์เพื่อใช้การซิงโครไนซ์ Mutex ดังนั้นคุณจะใช้คีย์ที่ซิงโครไนซ์ใน JVM ได้อย่างไร
1. การเป็นตัวแทนของ Bytecode ที่ซิงโครไนซ์:
มีไวยากรณ์ที่ซิงโครไนซ์ในตัวสองตัวในภาษา Java: 1. คำสั่งที่ซิงโครไนซ์; 2. วิธีการซิงโครไนซ์ สำหรับคำสั่งที่ซิงโครไนซ์เมื่อซอร์สโค้ด Java ถูกรวบรวมลงใน Bytecode โดย Javac, MonitorEnenter และ Monitorexit Bytecode คำแนะนำจะถูกแทรกตามลำดับที่ตำแหน่งและออกจากตำแหน่งของบล็อกการซิงโครไนซ์ วิธีการซิงโครไนซ์จะถูกแปลเป็นคำแนะนำการโทรแบบธรรมดาและคำแนะนำการส่งคืนเช่น: คำแนะนำ InvokeVirtual และ Areturn ไม่มีคำแนะนำพิเศษที่ระดับ VM bytecode เพื่อใช้วิธีการแก้ไขแบบซิงโครไนซ์ แต่ตำแหน่งแฟล็กที่ซิงโครไนซ์ 1 ในฟิลด์ Method Access_Flags ของวิธีการถูกวางไว้ในตารางเมธอดของไฟล์คลาสแสดงว่าวิธีการเป็นวิธีที่ซิงโครไนซ์และใช้วัตถุที่เรียกวิธีการหรือคลาสที่เป็นของวิธีการที่จะแสดง KLASS เป็นวัตถุล็อค
2. การเพิ่มประสิทธิภาพของล็อคใน JVM:
พูดง่ายๆคือ MonitorEnenter และ Monitorexit bytecode ใน JVM พึ่งพา Mutexlock ของระบบปฏิบัติการพื้นฐานเพื่อนำไปใช้ อย่างไรก็ตามเนื่องจากการใช้ mutexlock ต้องระงับเธรดปัจจุบันและสลับจากสถานะผู้ใช้เป็นสถานะเคอร์เนลเพื่อดำเนินการการสลับนี้มีราคาแพงมาก อย่างไรก็ตามในกรณีส่วนใหญ่ในความเป็นจริงวิธีการซิงโครไนซ์จะทำงานในสภาพแวดล้อมแบบเธรดเดี่ยว (สภาพแวดล้อมการแข่งขันแบบไม่มีล็อค) หาก Mutexlock ถูกเรียกว่าในแต่ละครั้งมันจะส่งผลกระทบอย่างจริงจังต่อประสิทธิภาพของโปรแกรม อย่างไรก็ตามใน JDK1.6 มีการปรับให้เหมาะสมจำนวนมากได้รับการแนะนำให้รู้จักกับการใช้งานล็อคเช่นล็อคการขจัดคราบล็อคการล็อคการล็อคน้ำหนักเบาล็อคลำเอียงการหมุนแบบปรับตัวและเทคโนโลยีอื่น ๆ เพื่อลดค่าใช้จ่ายของการล็อค
LOCKCOARSENING: นั่นคือลดการล็อคและล็อคที่ไม่จำเป็นขยายการล็อคอย่างต่อเนื่องหลายครั้งในล็อคที่มีช่วงที่ใหญ่กว่า
การกำจัดล็อค: ผ่านการวิเคราะห์หลบหนีโดยคอมไพเลอร์ JIT Runtime การป้องกันการล็อคบางส่วนจะถูกกำจัด ข้อมูลบางอย่างที่ไม่ได้แชร์โดยเธรดอื่นนอกบล็อกการซิงโครไนซ์ปัจจุบัน ผ่านการวิเคราะห์การหลบหนีสามารถจัดสรรพื้นที่วัตถุบนด้ายสแต็กท้องถิ่น (ในเวลาเดียวกันก็สามารถลดการเก็บรวบรวมขยะบนฮีป)
Lightweightlocking: การใช้งานล็อคนี้ขึ้นอยู่กับข้อสันนิษฐานว่าในกรณีจริงรหัสการซิงโครไนซ์ส่วนใหญ่ในโปรแกรมของเราโดยทั่วไปอยู่ในสถานะการแข่งขันที่ปราศจากล็อค (เช่นสภาพแวดล้อมการดำเนินการแบบเธรดเดี่ยว) ในกรณีของการแข่งขันฟรีล็อคสามารถหลีกเลี่ยงการโทรหา mutexes เฮฟวี่เวทในระดับระบบปฏิบัติการ ใน MonitorEnenter และ Monitorexit คุณจะต้องพึ่งพาคำสั่งอะตอม CAS เพื่อให้ได้มาซึ่งการซื้อและการปล่อยล็อค เมื่อมีการแข่งขันล็อคเธรดที่ไม่สามารถดำเนินการตามคำแนะนำ CAS จะเรียกระบบปฏิบัติการ Mutex เพื่อเข้าสู่สถานะการบล็อกและตื่นขึ้นมาเมื่อการล็อคถูกปล่อยออกมา (ขั้นตอนการประมวลผลเฉพาะจะกล่าวถึงในรายละเอียดด้านล่าง)
การลำเอียง: มันคือการหลีกเลี่ยงการดำเนินการตามคำแนะนำอะตอม CAS ที่ไม่จำเป็นในระหว่างการล็อคในกรณีของการแข่งขันที่ปราศจากล็อคเพราะแม้ว่าคำแนะนำอะตอม CAS มีค่าใช้จ่ายค่อนข้างน้อยเมื่อเทียบกับล็อคเฮฟวี่เวท
การปั่นแบบปรับตัว: เมื่อเธรดล้มเหลวในการดำเนินการ CAS ในระหว่างการได้รับล็อคที่มีน้ำหนักเบามันจะเข้าสู่การรอคอยที่วุ่นวายก่อนเข้าสู่ระบบปฏิบัติการเฮฟวี่เวทล็อค (MutexSemaphore) ที่เกี่ยวข้องกับจอภาพแล้วลองอีกครั้ง หากยังคงล้มเหลวหลังจากความพยายามจำนวนหนึ่งเซมาฟอร์ที่เกี่ยวข้องกับจอภาพ (เช่นล็อค mutex) จะถูกเรียกให้เข้าสู่สถานะการบล็อก
3. objectheader:
เมื่อสร้างวัตถุใน JVM ส่วนหัววัตถุขนาดคำสองคำจะถูกเพิ่มเข้ามาด้านหน้าของวัตถุ หนึ่งคำบนเครื่อง 32 บิตคือ 32 บิต เนื้อหาที่แตกต่างกันจะถูกเก็บไว้ใน Markworld ตามบิตสถานะที่แตกต่างกัน ดังที่แสดงในรูปด้านบนในล็อคที่มีน้ำหนักเบามาร์คเวิร์ดแบ่งออกเป็นสองส่วน ที่จุดเริ่มต้นล็อคคำถูกตั้งค่าเป็น hashcode และบิตสามบิตต่ำสุดแสดงถึงสถานะที่ล็อกคำอยู่ สถานะเริ่มต้นคือ 001 หมายถึงสถานะปลอดการล็อค Klassptr ชี้ไปที่ที่อยู่ที่แสดงโดยวัตถุที่มีคลาส bytecode อยู่ในเครื่องเสมือน ฟิลด์แสดงถึงฟิลด์อินสแตนซ์วัตถุต่อเนื่อง
4. monitorrecord:
Monitorrecord เป็นโครงสร้างข้อมูลส่วนตัวของเธรด แต่ละเธรดมีรายการ monitorrecords ที่มีอยู่และรายการที่มีอยู่ทั่วโลก ดังนั้นการใช้งาน monitorrecords เหล่านี้คืออะไร? วัตถุที่ถูกล็อคแต่ละชิ้นจะเชื่อมโยงกับ monitorrecord (ล็อกคำในส่วนหัวของวัตถุไปยังที่อยู่เริ่มต้นของ monitorrecord เนื่องจากที่อยู่นี้จัดเรียง 8BYTE จัดเรียง 8 บิตต่ำสุดสามบิตของล็อกคำเป็นบิตสถานะ) ในเวลาเดียวกันมีฟิลด์เจ้าของใน monitorrecord เพื่อเก็บตัวระบุที่ไม่ซ้ำกันของเธรดที่เป็นเจ้าของล็อคแสดงว่าล็อคถูกครอบครองโดยเธรดนี้ รูปต่อไปนี้แสดงโครงสร้างภายในของ monitorrecord:
เจ้าของ: NULL ที่จุดเริ่มต้นหมายความว่าไม่มีเธรดในปัจจุบันเป็นเจ้าของบันทึกการตรวจสอบ เมื่อเธรดเป็นเจ้าของล็อคได้สำเร็จจะบันทึกตัวตนที่ไม่ซ้ำกันของเธรดและเมื่อล็อคถูกปล่อยออกมามันจะถูกตั้งค่าเป็นโมฆะ
entryQ: เชื่อมโยงระบบ mutex (semaphore) เพื่อบล็อกเธรดทั้งหมดที่ล้มเหลวในการล็อคบันทึกการตรวจสอบ
RCTHIS: แสดงจำนวนเธรดทั้งหมดที่ถูกบล็อกหรือรออยู่ในบันทึกการตรวจสอบ
NEST: ใช้เพื่อใช้การนับล็อคอีกครั้ง
HashCode: บันทึกค่า hashcode ที่คัดลอกจากส่วนหัวของวัตถุ (อาจมีอายุ GC)
ผู้สมัคร: ใช้เพื่อหลีกเลี่ยงการปิดกั้นที่ไม่จำเป็นหรือรอให้เธรดตื่นขึ้นมาเพราะมีเพียงเธรดเดียวเท่านั้นที่สามารถเป็นเจ้าของล็อคได้สำเร็จในแต่ละครั้ง หากเธรดก่อนหน้านี้ที่ปล่อยล็อคจะปลุกการปิดกั้นหรือการรอเธรดทั้งหมดในแต่ละครั้งมันจะทำให้การสลับบริบทที่ไม่จำเป็น (จากการปิดกั้นเป็นความพร้อมแล้วบล็อกอีกครั้งเนื่องจากความล้มเหลวของล็อคการแข่งขัน) และนำไปสู่การเสื่อมสภาพของประสิทธิภาพอย่างรุนแรง ผู้สมัครมีค่าที่เป็นไปได้เพียงสองค่าเท่านั้น: 0 หมายความว่าไม่มีเธรดที่ต้องตื่นสูงถึง 1 หมายถึงการปลุกเธรดทายาทเพื่อแข่งขันเพื่อล็อค
5. การใช้งานเฉพาะของล็อคน้ำหนักเบา:
เธรดสามารถล็อควัตถุได้สองวิธี: 1. รับล็อคของวัตถุโดยการขยายวัตถุในสถานะที่ไม่มีล็อค (สถานะบิต 001); 2. วัตถุนั้นอยู่ในสถานะที่ขยายตัว (สถานะสถานะ 00) แต่ฟิลด์เจ้าของของบันทึกการตรวจสอบที่ชี้ไปที่ล็อกคำเป็นโมฆะดังนั้นคุณสามารถลองตั้งค่าเจ้าของให้เป็นตัวตนของตัวเองผ่านคำสั่งอะตอม CAS เพื่อรับล็อค
กระบวนการทั่วไปของการรับล็อค (MonitorEnter) มีดังนี้:
(1) เมื่อวัตถุอยู่ในสถานะที่ไม่มีล็อค (ค่าบันทึกคำเป็น hashCode บิตสถานะคือ 001) เธรดแรกจะได้รับบันทึกการตรวจสอบฟรีจากรายการบันทึกการตรวจสอบที่มีอยู่ ค่ารังและค่าเริ่มต้นของเจ้าของจะตั้งไว้ที่ 1 และการระบุตัวตนของเธรดเองตามลำดับ เมื่อบันทึกการตรวจสอบพร้อมแล้วเราจะติดตั้งที่อยู่เริ่มบันทึกของจอภาพไปยังฟิลด์ล็อกคำของส่วนหัววัตถุผ่านคำสั่งอะตอม CAS เพื่อขยาย (ข้อความต้นฉบับพองตัวฉันคิดว่าเหตุผลที่เรียกว่าพองตัวเป็นหลักเนื่องจากวัตถุถูกขยายออกจากการสกัด ในบทความนี้
(2) วัตถุได้รับการขยายและเธรดที่บันทึกไว้ในเจ้าของจะถูกระบุว่าเป็นเธรดที่ได้รับล็อคเอง นี่เป็นกรณีของการล็อคอีกครั้ง คุณเพียงแค่ต้องเพิ่ม 1 เพื่อทำรัง ไม่จำเป็นต้องมีการทำงานของอะตอมและมีประสิทธิภาพมาก
(3) วัตถุได้รับการขยาย แต่ค่าของเจ้าของเป็นโมฆะ สถานะนี้เกิดขึ้นเมื่อการปิดกั้นด้ายหรือรอล็อคถูกล็อคในเวลาเดียวกันเจ้าของคนก่อนหน้าของล็อคเพิ่งเปิดตัวล็อค ในเวลานี้หลายเธรดพยายามตั้งค่าเจ้าของให้เป็นตัวตนของตัวเองผ่านคำสั่ง Atomic CAS เพื่อให้ได้ล็อคในสถานะการแข่งขันแบบมัลติเธรด เธรดที่ล้มเหลวในการแข่งขันจะเข้าสู่เส้นทางการดำเนินการของกรณีที่สี่ (4)
(4) วัตถุอยู่ในสถานะขยายและเจ้าของไม่ได้เป็นโมฆะ (ล็อค) มันหมุนหลายครั้งก่อนที่จะเรียก mutex เฮฟวี่เวทของระบบปฏิบัติการ เมื่อถึงหลายครั้งหากล็อคยังไม่ได้รับสำเร็จก็ถึงเวลาที่จะเริ่มเข้าสู่สถานะการบล็อก ขั้นแรกให้เพิ่มค่าของ rfthis อะตอมโดย 1 เนื่องจากเธรดอื่น ๆ อาจทำลายความสัมพันธ์ระหว่างวัตถุและบันทึกการตรวจสอบในระหว่างการเพิ่ม 1 จึงจำเป็นต้องทำการเปรียบเทียบอีกครั้งหลังจากเพิ่ม 1 เพื่อให้แน่ใจว่าค่าของล็อคไม่ได้เปลี่ยนแปลง เมื่อพบว่ามีการเปลี่ยนแปลงกระบวนการ MonitorEnter จะต้องทำซ้ำ ในขณะเดียวกันก็พบว่าเจ้าของเป็นโมฆะหรือไม่ ถ้าเป็นเช่นนั้นจะเรียก CAS ให้เข้าร่วมในการแข่งขันล็อค หากการแข่งขันล็อคล้มเหลวมันจะเข้าสู่สถานะการบล็อก
กระบวนการทั่วไปของการปล่อยล็อค (monitorexit) มีดังนี้:
(1) ก่อนอื่นตรวจสอบว่าวัตถุอยู่ในสถานะขยายหรือไม่และเธรดเป็นเจ้าของล็อคหรือไม่ หากพบว่ามันผิดข้อยกเว้นจะถูกโยน;
(2) ตรวจสอบว่าฟิลด์รังมากกว่า 1 ถ้ามากกว่า 1 เพียงแค่ลดรังลง 1 และยังคงล็อคต่อไป หากเท่ากับ 1 ให้ป้อนขั้นตอน (3);
(3) ตรวจสอบว่า RFThis มากกว่า 0 หรือไม่ตั้งค่าเจ้าของเป็น NULL และตื่นขึ้นมาพร้อมกับด้ายที่ปิดกั้นหรือรอเพื่อพยายามรับการล็อคอีกครั้ง ถ้าเท่ากับ 0 มันจะเข้าสู่ขั้นตอน (4)
(4) deflate วัตถุปล่อยล็อคโดยการแทนที่ล็อกคำของวัตถุกลับไปที่ค่า HashCode ดั้งเดิมเพื่อปล่อยล็อคและนำบันทึกการตรวจสอบกลับไปที่เธรด
สรุป
การอ้างอิง: " ความเข้าใจในเชิงลึกเกี่ยวกับคุณสมบัติขั้นสูงและแนวทางปฏิบัติที่ดีที่สุดของ Java Virtual Machine JVM (Zhou Zhiming) "
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้เกี่ยวกับการวิเคราะห์ปัญหาการซิงโครไนซ์และการใช้งานของรายละเอียด JVM ฉันหวังว่ามันจะเป็นประโยชน์กับทุกคน หากมีข้อบกพร่องใด ๆ โปรดฝากข้อความไว้เพื่อชี้ให้เห็น ขอบคุณเพื่อนที่ให้การสนับสนุนเว็บไซต์นี้!