เมื่อเร็ว ๆ นี้ฉันพบสถานการณ์ที่เกิดขึ้นพร้อมกันในที่ทำงานซึ่งต้องล็อคเพื่อให้แน่ใจว่ามีความถูกต้องของตรรกะทางธุรกิจและประสิทธิภาพหลังจากล็อคไม่ควรได้รับผลกระทบมากเกินไป แนวคิดเริ่มต้นคือการล็อคข้อมูลผ่านคำหลักเช่นการประทับเวลาและ ID ดังนั้นจึงมั่นใจได้ว่าการประมวลผลข้อมูลประเภทต่าง ๆ พร้อมกัน ความละเอียดล็อคที่จัดทำโดย API ของ Java นั้นมีขนาดใหญ่เกินไปและมันยากที่จะตอบสนองความต้องการเหล่านี้ในเวลาเดียวกันดังนั้นฉันจึงเขียนส่วนขยายง่าย ๆ หลายอย่างด้วยตัวเอง ...
1. การล็อคส่วน
การวาดภาพบนแนวคิดการแบ่งส่วนของพร้อมกันมาพร้อมกับการล็อคจำนวนหนึ่งจะถูกสร้างขึ้นและเมื่อใช้งานล็อคที่สอดคล้องกันจะถูกส่งคืนตามคีย์ นี่คือประสิทธิภาพที่ง่ายที่สุดและมีประสิทธิภาพสูงสุดและนำกลยุทธ์การล็อคมาใช้ในที่สุดในการใช้งานหลายอย่าง รหัสมีดังนี้:
/*** การล็อคเซ็กเมนต์ระบบให้จำนวนล็อคดั้งเดิมจำนวนหนึ่งรับการล็อคที่สอดคล้องกันตามค่าแฮชของวัตถุที่เข้ามาและเพิ่มล็อค* หมายเหตุ: หากค่าแฮชของวัตถุที่จะล็อคการเปลี่ยนแปลงอาจทำให้ล็อคไม่สามารถปล่อยได้สำเร็จ !!! */คลาสสาธารณะ segmentlock <t> {กลุ่มจำนวนเต็มส่วนตัว = 16; // จำนวนเริ่มต้นของเซ็กเมนต์ส่วนตัวสุดท้าย hashmap <จำนวนเต็ม, reentrantlock> lockmap = new hashmap <> (); public segmentlock () {init (null, false); } public segmentLock (จำนวนเต็มนับ, Boolean Fair) {init (นับ, ยุติธรรม); } private void init (จำนวนเต็มนับ, Boolean Fair) {ถ้า (นับ! = null) {segments = counts; } สำหรับ (int i = 0; i <segments; i ++) {lockmap.put (i, ใหม่ reentrantlock (ยุติธรรม)); }} ล็อคโมฆะสาธารณะ (คีย์ t) {reentrantlock lock = lockmap.get ((key.hashCode () >>> 1) % เซ็กเมนต์); lock.lock (); } โมฆะสาธารณะปลดล็อค (คีย์ t) {reentrantlock lock = lockmap.get ((key.hashCode () >>> 1) % เซ็กเมนต์); lock.unlock (); -2. ล็อคแฮช
กลยุทธ์การล็อคครั้งที่สองที่พัฒนาขึ้นตามการล็อคที่แบ่งส่วนข้างต้นคือการล็อคที่ละเอียดอย่างละเอียด แต่ละวัตถุที่มีค่าแฮชที่แตกต่างกันสามารถรับล็อคอิสระของตัวเองได้ ในการทดสอบเมื่อรหัสล็อคถูกดำเนินการอย่างรวดเร็วประสิทธิภาพจะช้ากว่าการล็อคเซ็กเมนต์ประมาณ 30% หากมีการดำเนินงานที่ใช้เวลานานนานความรู้สึกของประสิทธิภาพควรจะดีขึ้น รหัสมีดังนี้:
คลาสสาธารณะ Hashlock <T> {บูลีนส่วนตัว isfair = false; ส่วนตัวรอบชิงชนะเลิศส่วนสุดท้าย <t> segmentLock = new segmentLock <> (); // การล็อคเซ็กเมนต์ส่วนตัวสุดท้ายพร้อมกันพร้อมกัน <t, lockinfo> lockmap = ใหม่พร้อมกันพร้อมกัน <> (); Public Hashlock () {} Public Hashlock (Boolean Fair) {isfair = fair; } ล็อคโมฆะสาธารณะ (คีย์ T) {Lockinfo Lockinfo; segmentlock.lock (คีย์); ลอง {lockinfo = lockmap.get (คีย์); if (lockinfo == null) {lockinfo = ใหม่ lockinfo (isfair); lockmap.put (คีย์, lockinfo); } else {lockinfo.count.incrementandget (); }} ในที่สุด {segmentlock.unlock (คีย์); } lockinfo.lock.lock (); } โมฆะสาธารณะปลดล็อค (คีย์ t) {lockinfo lockinfo = lockmap.get (คีย์); if (lockinfo.count.get () == 1) {segmentlock.lock (คีย์); ลอง {ถ้า (lockinfo.count.get () == 1) {lockmap.remove (คีย์); }} ในที่สุด {segmentlock.unlock (คีย์); }} lockinfo.count.decrementandget (); lockinfo.unlock (); } คลาสคงที่คลาสคงที่ lockinfo {ล็อคสาธารณะ reentrantlock; Public Atomicinteger Count = Atomicinteger ใหม่ (1); Private Lockinfo (Boolean Fair) {this.lock = ใหม่ reentrantlock (ยุติธรรม); } โมฆะสาธารณะล็อค () {this.lock.lock (); } โมฆะสาธารณะปลดล็อค () {this.lock.unlock (); -3. ล็อคอ้างอิงที่อ่อนแอ
ล็อคแฮชมักจะมีข้อบกพร่องอยู่เสมอเพราะล็อคที่แบ่งเป็นส่วน ๆ ได้รับการแนะนำเพื่อให้แน่ใจว่าการซิงโครไนซ์ของการสร้างล็อคและการทำลายล้างดังนั้นล็อคที่สามจึงถูกเขียนขึ้นเพื่อแสวงหาประสิทธิภาพที่ดีขึ้นและล็อคที่ดีกว่า แนวคิดของการล็อคนี้คือการสร้างล็อคด้วยความช่วยเหลือของการอ้างอิงที่อ่อนแอใน Java และส่งมอบการทำลายล้างของล็อคให้กับคอลเลกชันขยะของ JVM เพื่อหลีกเลี่ยงการบริโภคเพิ่มเติม
เป็นเรื่องน่าเสียใจเล็กน้อยที่ใช้มาพร้อมกันว่าใช้เป็นตู้คอนเทนเนอร์ล็อคจึงไม่สามารถกำจัดล็อคแบบแบ่งส่วนได้อย่างแท้จริง ประสิทธิภาพของล็อคนี้เร็วกว่า Hashlock ประมาณ 10% รหัสล็อค:
/*** ล็อคอ้างอิงที่อ่อนแอซึ่งให้ฟังก์ชั่นการล็อคอิสระสำหรับแต่ละแฮชอิสระ*/คลาสสาธารณะ Weakhashlock <t> {ส่วนตัวพร้อมกันกับการใช้งาน Map <t, Weaklockref <t, reentrantlock >> lockmap = ใหม่พร้อมกัน hashmap <> (); Private ReferenceQueue <reentrantlock> queue = new ReferenceQueue <> (); สาธารณะ reentrantlock get (t คีย์) {ถ้า (lockmap.size ()> 1000) {clearemptyref (); } beakreference <SreentRantLock> lockref = lockmap.get (คีย์); reentrantlock lock = (lockref == null? null: lockref.get ()); ในขณะที่ (lock == null) {lockmap.putifabsent (คีย์, new weaklockref <> (ใหม่ reentrantlock (), คิว, คีย์, คีย์)); lockref = lockmap.get (คีย์); lock = (lockref == null? null: lockref.get ()); if (lock! = null) {return lock; } clearemptyref (); } return lock; } @suppresswarnings ("ไม่ได้ตรวจสอบ") โมฆะส่วนตัว Clearemptyref () {อ้างอิง <? ขยาย reentrantlock> อ้างอิง; ในขณะที่ ((ref = queue.poll ())! = null) {weaklockref <t,? ขยาย reentrantlock> weaklockref = (weaklockref <t,? ขยาย reentrantlock>) อ้างอิง; lockmap.remove (weaklockref.key); }} คลาสสุดท้ายคงที่ระดับสุดท้าย Weaklockref <t, k> ขยายความอ่อนแอ <k> {คีย์สุดท้าย t; Private Weaklockref (K อ้างอิง K, ReferenceQueue <? super k> q, t คีย์) {super (อ้างอิง, q); this.key = key; -postscript
ตอนแรกฉันต้องการใช้ LocksUpport และ AQS เพื่อใช้งานล็อคที่ดี อย่างที่ฉันเขียนฉันพบว่าสิ่งที่นำไปใช้ไม่ได้แตกต่างจากสิ่งที่อยู่ในล็อคพื้นเมืองของ Java ดังนั้นฉันจึงเลิกการห่อหุ้มล็อคของ Java ซึ่งเสียเวลาไปมาก
ในความเป็นจริงหลังจากใช้ล็อคที่ละเอียดเหล่านี้เรามีแนวคิดใหม่เช่นการส่งข้อมูลไปยังเธรดพิเศษผ่านแนวคิดที่แบ่งกลุ่มซึ่งสามารถลดเวลาในการปิดกั้นของเธรดจำนวนมากและปล่อยให้การสำรวจในอนาคต ...