1. ล็อคเฮฟวี่เวท
ในบทความก่อนหน้านี้เราแนะนำการใช้งานของการซิงโครไนซ์และหลักการดำเนินการ ตอนนี้เราควรรู้ว่าการซิงโครไนซ์ถูกนำไปใช้ผ่านล็อคจอภาพภายในวัตถุ อย่างไรก็ตามการล็อคจอภาพนั้นถูกนำไปใช้เป็นหลักโดยอาศัยการล็อค Mutex ของระบบปฏิบัติการพื้นฐาน ระบบปฏิบัติการจำเป็นต้องสลับระหว่างเธรดจากสถานะผู้ใช้เป็นสถานะหลัก สิ่งนี้มีค่าใช้จ่ายสูงมากและการแปลงระหว่างรัฐใช้เวลาค่อนข้างนาน นี่คือเหตุผลที่ซิงโครไนซ์ไม่มีประสิทธิภาพ ดังนั้นเราจึงเรียกว่าล็อคนี้ซึ่งขึ้นอยู่กับการใช้งานระบบปฏิบัติการล็อค "ล็อคเฮฟวี่เวท" แกนกลางของการเพิ่มประสิทธิภาพต่าง ๆ ที่ทำเพื่อซิงโครไนซ์ใน JDK คือการลดการใช้งานล็อคเฮฟวี่เวทนี้ หลังจาก JDK1.6 เพื่อลดการใช้ประสิทธิภาพที่เกิดจากการได้รับและปล่อยล็อคและปรับปรุงประสิทธิภาพ "ล็อคน้ำหนักเบา" และ "ล็อคลำเอียง" ได้รับการแนะนำ
2. ล็อคน้ำหนักเบา
สถานะล็อคสี่ประเภท: สถานะฟรีล็อคล็อคลำเอียงล็อคน้ำหนักเบาและล็อคเฮฟวี่เวท ด้วยการแข่งขันของล็อคล็อคสามารถอัพเกรดจากล็อคลำเอียงเป็นล็อคน้ำหนักเบาและจากนั้นอัพเกรดล็อคเฮฟวี่เวท (แต่การอัพเกรดล็อคเป็นทางเดียวซึ่งหมายความว่าพวกเขาสามารถอัพเกรดจากต่ำถึงสูงและจะไม่มีการย่อยสลายล็อค) ใน JDK 1.6 การล็อคอคติและการล็อคน้ำหนักเบาจะเปิดใช้งานโดยค่าเริ่มต้น นอกจากนี้เรายังสามารถปิดใช้งาน Bias Lock โดย -xx: -useBiasedlocking สถานะล็อคจะถูกบันทึกไว้ในไฟล์ส่วนหัวของวัตถุโดยใช้ JDK 32 บิตเป็นตัวอย่าง:
สถานะล็อค | 25 บิต | 4 บิต | 1 บิต | 2 บิต | ||
23 บิต | 2 บิต | ล็อคลำเอียงหรือไม่? | ล็อคบิตธง | |||
ล็อคน้ำหนักเบา | ตัวชี้เพื่อล็อคเร็กคอร์ดในสแต็ก | 00 | ||||
ล็อคเฮฟวี่เวท | ตัวชี้ไปยัง Mutex (ล็อคเฮฟวี่เวท) | 10 | ||||
แท็ก GC | โมฆะ | 11 | ||||
ล็อคบวก | ID เธรด | ยุค | วิชาอายุ | 1 | 01 | |
ไม่มีล็อค | hashcode ของวัตถุ | วิชาอายุ | 0 | 01 | ||
"Lightweight" สัมพันธ์กับล็อคแบบดั้งเดิมที่ใช้ระบบปฏิบัติการ mutexes อย่างไรก็ตามมันเป็นสิ่งสำคัญที่จะเน้นก่อนว่าล็อคน้ำหนักเบาไม่ได้ใช้เพื่อแทนที่ล็อคเฮฟวี่เวท ความตั้งใจดั้งเดิมคือการลดการใช้ประสิทธิภาพที่เกิดขึ้นจากการใช้ล็อคเฮฟวี่เวทแบบดั้งเดิมโดยไม่ต้องแข่งขันแบบมัลติเธรด ก่อนที่จะอธิบายกระบวนการดำเนินการของล็อคน้ำหนักเบาเราเข้าใจก่อนว่าสถานการณ์ที่ปรับให้เข้ากับล็อคน้ำหนักเบาเป็นกรณีที่เธรดสลับกันเรียกใช้บล็อกซิงโครนัส หากล็อคเดียวกันเข้าถึงได้ในเวลาเดียวกันล็อคน้ำหนักเบาจะขยายไปสู่ล็อคเฮฟวี่เวท
1. กระบวนการล็อคของล็อคน้ำหนักเบา
(1) เมื่อรหัสเข้าสู่บล็อกการซิงโครไนซ์หากสถานะการล็อคของวัตถุซิงโครไนซ์นั้นปราศจากล็อค (ธงล็อคคือสถานะ "01" ไม่ว่าจะเป็นการล็อคแบบลำเอียงคือ "0") เครื่องเสมือนจริงจะสร้างพื้นที่ที่เรียกว่า ในเวลานี้สถานะของสแต็กเธรดและส่วนหัววัตถุจะแสดงในรูปที่ 2.1
(2) คัดลอกคำทำเครื่องหมายในส่วนหัวของวัตถุและคัดลอกไปยังบันทึกล็อค
(3) หลังจากสำเนาสำเร็จแล้วเครื่องเสมือนจะใช้การดำเนินการ CAS เพื่อพยายามอัปเดตคำทำเครื่องหมายของวัตถุไปยังตัวชี้เพื่อล็อคเร็กคอร์ดและชี้ตัวชี้เจ้าของในบันทึกการล็อคเป็นคำทำเครื่องหมายวัตถุ หากการอัปเดตสำเร็จให้ดำเนินการขั้นตอน (3) มิฉะนั้นจะดำเนินการขั้นตอน (4)
(4) หากการดำเนินการอัปเดตนี้สำเร็จแล้วเธรดจะมีล็อคของวัตถุและธงล็อคของคำทำเครื่องหมายวัตถุถูกตั้งค่าเป็น "00" ซึ่งหมายความว่าวัตถุนั้นอยู่ในสถานะล็อคน้ำหนักเบา ในเวลานี้สถานะของสแต็กเธรดและหัววัตถุจะแสดงในรูปที่ 2.2
(5) หากการดำเนินการอัปเดตนี้ล้มเหลวเครื่องเสมือนจะตรวจสอบก่อนว่าเครื่องหมายคำของวัตถุจะชี้ไปที่เฟรมสแต็กของเธรดปัจจุบันหรือไม่ ถ้าเป็นเช่นนั้นหมายความว่าเธรดปัจจุบันมีการล็อคของวัตถุอยู่แล้วและจากนั้นสามารถป้อนบล็อกการซิงโครไนซ์โดยตรงเพื่อดำเนินการต่อ มิฉะนั้นหลายเธรดจะแข่งขันกันเพื่อล็อคและล็อคน้ำหนักเบาจะขยายไปสู่การล็อคเฮฟวี่เวทและค่าสถานะของธงล็อคจะกลายเป็น "10" ตัวชี้ไปยังล็อคเฮฟวี่เวท (mutex) จะถูกเก็บไว้ใน Mark Word และเธรดที่รอการล็อคจะเข้าสู่สถานะการบล็อก เธรดปัจจุบันพยายามใช้สปินเพื่อรับล็อค การหมุนคือการหลีกเลี่ยงการปิดกั้นเธรดและใช้ลูปเพื่อรับล็อค
รูปที่ 2.1 สถานะของสแต็กและวัตถุก่อนการดำเนินการล็อคน้ำหนักเบา
รูปที่ 2.2 สถานะของสแต็กและวัตถุหลังจากการทำงานของล็อคน้ำหนักเบา
2. การปลดล็อคกระบวนการล็อคน้ำหนักเบา:
(1) พยายามแทนที่วัตถุคำทำเครื่องหมายที่ถูกแทนที่ในเธรดผ่านการดำเนินการ CAS
(2) หากการเปลี่ยนสำเร็จกระบวนการซิงโครไนซ์ทั้งหมดจะเสร็จสมบูรณ์
(3) หากการแทนที่ล้มเหลวหมายความว่าเธรดอื่น ๆ ได้พยายามที่จะได้รับการล็อค (การล็อคได้ขยายตัวในเวลานี้) แล้วเธรดที่ถูกระงับจะต้องตื่นขึ้นมาในขณะที่ปล่อยล็อค
3. ล็อคบวก
การแนะนำของการล็อคอคติคือการลดเส้นทางการดำเนินการล็อคน้ำหนักเบาที่ไม่จำเป็นโดยไม่ต้องมีการแข่งขันแบบมัลติเธรดเนื่องจากการได้มาและการปล่อยล็อคน้ำหนักเบาขึ้นอยู่กับคำแนะนำอะตอม CAS หลายคำสั่ง การบริโภคคำแนะนำอะตอม CAS) ดังที่ได้กล่าวไว้ข้างต้นล็อคน้ำหนักเบาจะใช้เพื่อปรับปรุงประสิทธิภาพเมื่อเธรดสลับกันเรียกใช้บล็อกแบบซิงโครนัสในขณะที่ล็อคแบบเอนเอียงจะใช้เพื่อปรับปรุงประสิทธิภาพเพิ่มเติมเมื่อเธรดหนึ่งใช้งานบล็อกซิงโครนัส
1. กระบวนการรับล็อคลำเอียง:
(1) เข้าถึงว่าการตั้งค่าสถานะของการล็อคอคติในเครื่องหมายเครื่องหมายถูกตั้งค่าเป็น 1 และไม่ว่าธงล็อคจะเป็น 01 - ยืนยันว่าเป็นสถานะที่น่าเชื่อถือหรือไม่
(2) หากเป็นสถานะที่มีคุณสมบัติได้ให้ทดสอบว่าเธรด ID ชี้ไปที่เธรดปัจจุบันหรือไม่ ถ้าเป็นเช่นนั้นให้ป้อนขั้นตอน (5) มิฉะนั้นป้อนขั้นตอน (3)
(3) หาก ID เธรดไม่ชี้ไปที่เธรดปัจจุบันการล็อคจะถูกแข่งขันผ่านการดำเนินการ CAS หากการแข่งขันประสบความสำเร็จให้ตั้งค่า ID เธรดในเครื่องหมายเครื่องหมายเป็นรหัสเธรดปัจจุบันและดำเนินการ (5); หากการแข่งขันล้มเหลวให้ดำเนินการ (4)
(4) หาก CAS ล้มเหลวในการรับล็อคอคตินั่นหมายความว่ามีการแข่งขัน เมื่อถึง SafePoint ทั่วโลกเธรดที่ได้รับการล็อคอคติจะถูกระงับ ล็อคอคติจะถูกอัพเกรดเป็นล็อคที่มีน้ำหนักเบาและเธรดที่ถูกบล็อกที่ SafePoint ยังคงดำเนินการรหัสการซิงโครไนซ์
(5) เรียกใช้รหัสการซิงโครไนซ์
2. การเปิดตัวล็อคลำเอียง:
การเพิกถอนการล็อคลำเอียงถูกกล่าวถึงในขั้นตอนที่สี่ข้างต้น ล็อคลำเอียงจะปล่อยล็อคเมื่อเธรดอื่น ๆ พยายามที่จะแข่งขันสำหรับการล็อคลำเอียงและเธรดจะไม่ปล่อยล็อคลำเอียงอย่างแข็งขัน การยกเลิกการล็อคแบบลำเอียงต้องรอจุดรักษาความปลอดภัยทั่วโลก (ไม่มีการดำเนินการทางไบต์ในเวลานี้) ก่อนอื่นจะหยุดเธรดด้วยการล็อคลำเอียงให้ตรวจสอบว่าวัตถุล็อคอยู่ในสถานะล็อคแล้วกลับไปที่การปลดล็อค (บิตธงคือ "01") หรือล็อคน้ำหนักเบา (บิตธงคือ "00") หลังจากยกเลิกการล็อคลำเอียง
3. การแปลงระหว่างล็อคเฮฟวี่เวท, ล็อคน้ำหนักเบาและล็อคอคติ
รูปที่ 2.3 แผนภาพการแปลงของทั้งสาม
ภาพนี้ส่วนใหญ่เป็นบทสรุปของเนื้อหาข้างต้น หากคุณมีความเข้าใจที่ดีเกี่ยวกับเนื้อหาข้างต้นรูปภาพควรเข้าใจง่าย
4. การเพิ่มประสิทธิภาพอื่น ๆ
1. การปั่นแบบปรับตัว: จากกระบวนการรับล็อคน้ำหนักเบาเรารู้ว่าเมื่อเธรดล้มเหลวในการดำเนินการ CAS ในระหว่างการได้รับล็อคน้ำหนักเบาจำเป็นต้องได้รับล็อคเฮฟวี่เวทผ่านการหมุน ปัญหาคือการหมุนต้องใช้การบริโภค CPU หากไม่สามารถรับล็อคได้เธรดจะอยู่ในสถานะสปินและทรัพยากร CPU ของเสียอย่างไร้ประโยชน์ วิธีที่ง่ายที่สุดในการแก้ปัญหานี้คือการระบุจำนวนสปินตัวอย่างเช่นปล่อยให้มันหมุนรอบ 10 ครั้งและป้อนสถานะการปิดกั้นหากไม่ได้รับการล็อค แต่ JDK ใช้วิธีการที่ชาญฉลาดกว่า - สปินแบบปรับตัว พูดง่ายๆคือหากเธรดสำเร็จจำนวนสปินจะเพิ่มขึ้นในครั้งต่อไปและหากการหมุนล้มเหลวจำนวนสปินจะลดลง
2. ล็อค coarsening: แนวคิดของความหยาบล็อคควรจะเข้าใจง่ายขึ้นซึ่งคือการรวมการล็อคและปลดล็อกการดำเนินการเชื่อมต่อเข้าด้วยกันหลายครั้งในครั้งเดียวขยายการล็อคอย่างต่อเนื่องหลายครั้งในล็อคที่มีช่วงที่ใหญ่กว่า ตัวอย่างเช่น:
แพ็คเกจ com.paddx.test.string; คลาสสาธารณะ StringBufferTest {StringBuffer StringBuffer = new StringBuffer (); ต่อผนวกโมฆะสาธารณะ () {StringBuffer.Append ("A"); StringBuffer.Append ("B"); StringBuffer.Append ("C"); -ที่นี่ทุกครั้งที่คุณเรียกใช้เมธอด stringbuffer.append การล็อคและการปลดล็อค หากเครื่องเสมือนตรวจจับชุดของการล็อคและปลดล็อคการดำเนินการบนวัตถุเดียวกันมันจะรวมเข้ากับการล็อคและการปลดล็อคที่มีขนาดใหญ่ขึ้นนั่นคือการล็อคจะดำเนินการในวิธีการผนวกแรกและการปลดล็อคจะดำเนินการหลังจากวิธีการภาคผนวกสุดท้ายเสร็จสิ้น
3. การกำจัดล็อค: การกำจัดล็อคหมายถึงการลบการล็อคที่ไม่จำเป็น ตามเทคโนโลยีการหลบหนีของรหัสหากมีการพิจารณาว่าชิ้นส่วนของรหัสและข้อมูลบนฮีปจะไม่หลบหนีจากเธรดปัจจุบันก็สามารถพิจารณาได้ว่ารหัสชิ้นนี้เป็นเธรดที่ปลอดภัยและไม่จำเป็นต้องล็อค ดูโปรแกรมต่อไปนี้:
แพ็คเกจ com.paddx.test.concurrent; คลาสสาธารณะ SynchronizedTest02 {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {synchronizedTest02 test02 = ใหม่ synchronizedTest02 (); // เริ่มการอุ่นเครื่องสำหรับ (int i = 0; i <10,00000; i ++) {i ++; } Long Start = System.currentTimeMillis (); สำหรับ (int i = 0; i <100000000; i ++) {test02.append ("abc", "def"); } system.out.println ("time =" + (system.currentTimeMillis () - เริ่มต้น)); } ต่อท้ายโมฆะสาธารณะ (String str1, String str2) {stringbuffer sb = new Stringbuffer (); sb.append (str1) .append (str2); -แม้ว่าภาคผนวกของ StringBuffer เป็นวิธีการแบบซิงโครนัส แต่ StringBuffer ในโปรแกรมนี้เป็นของตัวแปรท้องถิ่นและจะไม่หลบหนีจากวิธีการ ดังนั้นกระบวนการนี้จึงเป็นเรื่องที่ปลอดภัยและสามารถกำจัดล็อคได้ นี่คือผลลัพธ์ของการดำเนินการในท้องถิ่นของฉัน:
เพื่อลดผลกระทบของปัจจัยอื่น ๆ ให้ลดอคติ (-xx: -usebiasedlocking) ถูกปิดใช้งานที่นี่ ผ่านโปรแกรมข้างต้นจะเห็นได้ว่าประสิทธิภาพได้รับการปรับปรุงอย่างมากหลังจากการล็อคถูกกำจัด
หมายเหตุ: ผลลัพธ์การดำเนินการอาจแตกต่างกันระหว่าง JDK รุ่นที่แตกต่างกัน เวอร์ชัน JDK ที่ฉันใช้ที่นี่คือ 1.6
5. สรุป
บทความนี้มุ่งเน้นไปที่การเพิ่มประสิทธิภาพของการซิงโครไนซ์ใน JDK เช่นล็อคน้ำหนักเบาและล็อคลำเอียง แต่ล็อคทั้งสองนี้ไม่สมบูรณ์โดยไม่มีข้อบกพร่อง ตัวอย่างเช่นเมื่อการแข่งขันดุร้ายมันจะไม่เพียง แต่ล้มเหลวในการปรับปรุงประสิทธิภาพ แต่จะลดประสิทธิภาพเนื่องจากมีกระบวนการอัพเกรดล็อคเพิ่มเติม ในเวลานี้มีความจำเป็นที่จะต้องปิดใช้งานการล็อคแบบลำเอียงผ่าน -xx: -usebiasedlocking นี่คือการเปรียบเทียบล็อคเหล่านี้:
ล็อค | ข้อได้เปรียบ | ข้อบกพร่อง | สถานการณ์ที่เกี่ยวข้อง |
ล็อคบวก | การล็อคและปลดล็อคไม่จำเป็นต้องใช้การบริโภคเพิ่มเติมและมีช่องว่างนาโนวินาทีเมื่อเทียบกับการดำเนินการแบบอะซิงโครนัส | หากมีการแข่งขันล็อคระหว่างเธรดมันจะทำให้เกิดการใช้การเพิกถอนการล็อคเพิ่มเติม | เหมาะสำหรับสถานการณ์ที่มีเพียงเธรดเดียวเท่านั้นที่เข้าถึงบล็อกซิงโครนัส |
ล็อคน้ำหนักเบา | เธรดการแข่งขันจะไม่บล็อกซึ่งช่วยเพิ่มความเร็วในการตอบสนองของโปรแกรม | หากเธรดที่ไม่เคยได้รับการแข่งขันล็อคจะใช้ CPU | ตามเวลาตอบสนอง การดำเนินการบล็อกแบบซิงโครนัสนั้นเร็วมาก |
ล็อคเฮฟวี่เวท | การแข่งขันเธรดไม่ได้ใช้การหมุนและไม่ใช้ CPU | การปิดกั้นด้ายเวลาตอบสนองช้า | การติดตามปริมาณงาน ความเร็วในการดำเนินการบล็อกซิงโครไนซ์ค่อนข้างยาว |