0. เกี่ยวกับ mutex
ล็อค Mutex ที่เรียกว่าหมายถึงล็อคที่สามารถมีเธรดได้ทีละเส้นเท่านั้น ก่อน JDK1.5 เรามักจะใช้กลไกที่ซิงโครไนซ์เพื่อควบคุมการเข้าถึงหลายเธรดไปยังทรัพยากรที่ใช้ร่วมกัน ตอนนี้ล็อคให้การดำเนินการล็อคที่กว้างกว่ากลไกที่ซิงโครไนซ์ ความแตกต่างที่สำคัญระหว่างกลไกการล็อคและการซิงโครไนซ์:
กลไกที่ซิงโครไนซ์ให้การเข้าถึงล็อคการตรวจสอบโดยนัยที่เกี่ยวข้องกับแต่ละวัตถุและบังคับให้การล็อคการได้มาและปล่อยทั้งหมดจะปรากฏในโครงสร้างบล็อก เมื่อได้มาหลายครั้งล็อคพวกเขาจะต้องได้รับการปล่อยตัวตามลำดับย้อนกลับ กลไกที่ซิงโครไนซ์ปล่อยล็อคโดยปริยาย ตราบใดที่รหัสทำงานโดยเธรดเกินขอบเขตของบล็อกคำสั่งซิงโครไนซ์ล็อคจะถูกปล่อยออกมา กลไกการล็อคจะต้องเรียกวิธีการปลดล็อค () ของวัตถุล็อคอย่างชัดเจนเพื่อปล่อยล็อคซึ่งให้ความเป็นไปได้สำหรับการได้มาและการปล่อยล็อคที่จะไม่ปรากฏในโครงสร้างบล็อกเดียวกันและปล่อยล็อคตามลำดับฟรีมากขึ้น
1. บทนำสู่ reentrantlock
Reentrantlock เป็นล็อค mutex reentrant หรือที่เรียกว่า "ล็อคพิเศษ"
ตามชื่อแนะนำล็อค reentrantlock สามารถจับได้เพียงหนึ่งล็อคเธรดที่จุดเวลาเดียวกัน ในขณะที่ reentrant หมายความว่าการล็อค reentrantlock สามารถรับได้หลายครั้งด้วยเธรดเดียว
Reentrantlock แบ่งออกเป็น "Fair Lock" และ "ไม่เป็นธรรมล็อค" ความแตกต่างของพวกเขาสะท้อนให้เห็นว่ากลไกการได้รับล็อคนั้นยุติธรรมหรือไม่ "ล็อค" คือการปกป้องทรัพยากรการแข่งขันและป้องกันหลายเธรดจากการใช้งานเธรดในเวลาเดียวกันและข้อผิดพลาด reentrantlock สามารถรับได้โดยหนึ่งเธรดในเวลาเดียวกัน (เมื่อเธรดได้รับ "ล็อค" เธรดอื่น ๆ จะต้องรอ); Reentraantlock จัดการกระทู้ทั้งหมดที่ได้รับการล็อคผ่านคิวรอ FIFO ภายใต้กลไกของ "ล็อคยุติธรรม" เธรดคิวขึ้นเพื่อรับล็อคตามลำดับ; ในขณะที่ "Non-Fair Lock" จะได้รับล็อคโดยไม่คำนึงว่ามันเป็นจุดเริ่มต้นของคิวหรือไม่
รายการฟังก์ชัน reentrantlock
// สร้าง reentrantlock ซึ่งเป็น "ล็อคที่ไม่เป็นธรรม" โดยค่าเริ่มต้น reentrantlock () // นโยบายการสร้างเป็น reentrantlock ของ Fair หากยุติธรรมเป็นความจริงก็หมายความว่ามันเป็นล็อคที่ยุติธรรมและถ้าความยุติธรรมเป็นเท็จก็หมายความว่ามันเป็นล็อคที่ไม่ใช่คู่ Reentrantlock (Boolean Fair) // สอบถามจำนวนครั้งที่เธรดปัจจุบันยังคงล็อคนี้ไว้ int getholdCount () // ส่งคืนเธรดที่เป็นเจ้าของล็อคนี้ในปัจจุบันและหากล็อคนี้ไม่ได้เป็นเจ้าของโดยเธรดใด ๆ ให้ส่งคืนค่า NULL เธรดที่ได้รับการป้องกัน getowner () // ส่งคืนคอลเลกชันที่มีเธรดที่อาจรอรับการล็อคนี้ คอลเลกชันที่ได้รับการป้องกัน <stread> getqueuedThreads () // ส่งคืนจำนวนเธรดโดยประมาณที่รอรับการล็อคนี้ int getqueuelength () // ส่งคืนคอลเลกชันที่มีเธรดเหล่านั้นที่อาจรอเงื่อนไขที่เกี่ยวข้องกับการล็อคนี้ คอลเลกชันที่ได้รับการป้องกัน <stread> getWaitingThreads (เงื่อนไขเงื่อนไข) // ส่งคืนการประมาณการเธรดรอเงื่อนไขที่กำหนดที่เกี่ยวข้องกับการล็อคนี้ int getwaitqueuelength (เงื่อนไขเงื่อนไข) // คำถามว่าเธรดที่กำหนดกำลังรอที่จะได้รับการล็อคนี้หรือไม่ บูลีน hasqueuedThread (เธรดเธรด) // คำถามว่าเธรดบางตัวกำลังรอที่จะได้รับการล็อคนี้หรือไม่ บูลีน hasqueuedThreads () // คำถามว่าบางเธรดกำลังรอเงื่อนไขที่เกี่ยวข้องกับล็อคนี้หรือไม่ Boolean Haswaiters (เงื่อนไขเงื่อนไข) // return true ถ้ามันเป็น "ล็อคยุติธรรม" มิฉะนั้นจะส่งคืนเท็จ บูลีน isfair () // คำถามว่าเธรดปัจจุบันยังคงล็อคนี้หรือไม่ บูลีน isheldbycurrentthread () // คำถามว่าล็อคนี้จัดขึ้นโดยเธรดใด ๆ หรือไม่ บูลีน islocked () // รับล็อค Void Lock () // หากเธรดปัจจุบันไม่ถูกขัดจังหวะการล็อคจะได้รับ เป็นโมฆะล็อคได้ () // ส่งคืนอินสแตนซ์เงื่อนไขที่ใช้ในการใช้กับอินสแตนซ์ล็อคนี้ เงื่อนไข newCondition () // จะได้รับการล็อคเท่านั้นหากไม่ได้อยู่ในเธรดอื่นระหว่างการโทร บูลีน trylock () // หากล็อคไม่ได้ถูกจับโดยเธรดอื่นภายในเวลารอที่กำหนดและเธรดปัจจุบันจะไม่ถูกขัดจังหวะการล็อคจะได้มา Boolean Trylock (Long Timeout, TimeUnit Unit) // พยายามที่จะปล่อยล็อคนี้ โมฆะปลดล็อค ()
2. ตัวอย่าง reentrantlock
โดยการเปรียบเทียบ "ตัวอย่าง 1" และ "ตัวอย่าง 2" เราสามารถเข้าใจบทบาทของการล็อคและปลดล็อคได้อย่างชัดเจน
2.1 ตัวอย่าง 1
นำเข้า java.util.concurrent.locks.lock; นำเข้า java.util.concurrent.locks.reentrantlock; // locktest1.java// คลังเก็บของที่เก็บ {ขนาด int ส่วนตัว; // จำนวนจริงของล็อคล็อคส่วนตัวที่เก็บ; // คลังสาธารณะล็อคพิเศษ () {this.size = 0; this.lock = ใหม่ reentrantlock (); } โมฆะสาธารณะผลิต (int val) {lock.lock (); ลอง {size += val; System.out.printf ("%s ผลิต (%d) -> size =%d/n", thread.currentthread (). getName (), val, ขนาด); } ในที่สุด {lock.unlock (); }} การบริโภคโมฆะสาธารณะ (int val) {lock.lock (); ลอง {size -= val; System.out.printf ("%s การบริโภค (%d) <- size =%d/n", thread.currentthread (). getName (), val, ขนาด); } ในที่สุด {lock.unlock (); - // ผู้ผลิตคลาสผู้ผลิต {เงินฝากภาคเอกชน; ผู้ผลิตสาธารณะ (สถานีรถไฟ) {this.depot = เงินฝาก; } // สินค้าอุปโภคบริโภค: สร้างเธรดใหม่เพื่อผลิตผลิตภัณฑ์ลงในคลังสินค้า โมฆะสาธารณะผลิต (สุดท้าย int val) {thread ใหม่ () {public void run () {depent.produce (val); } }.เริ่ม(); }} // ลูกค้าคลาสผู้บริโภค {เงินฝากภาคเอกชน ลูกค้าสาธารณะ (สถานีรถไฟ) {this.depot = เงินฝาก; } // สินค้าอุปโภคบริโภค: สร้างเธรดใหม่เพื่อใช้ผลิตภัณฑ์จากคลังสินค้า การบริโภคโมฆะสาธารณะ (int val สุดท้าย) {thread ใหม่ () {public void run () {depot.consume (val); } }.เริ่ม(); }} คลาสสาธารณะ LockTest1 {โมฆะสาธารณะคงที่หลัก (สตริง [] args) {Depot mdepot = ใหม่สถานีรถไฟ (); ผู้ผลิต mpro = ผู้ผลิตใหม่ (MDEPOT); MCUS ลูกค้า = ลูกค้าใหม่ (MDEPOT); mpro.produce (60); mpro.produce (120); mcus.consume (90); mcus.consume (150); mpro.produce (110); - ผลการทำงาน:
Thread-0 ผลิต (60)-> size = 60Thread-1 ผลิต (120)-> size = 180Thread-3 Consume (150) <-size = 30 Thread-2 Consume (90) <-size = -60Thread-4 ผลิต (110)-> size = 50
การวิเคราะห์ผลลัพธ์:
(1) สถานีรถไฟเป็นคลังสินค้า สินค้าสามารถผลิตเข้าไปในคลังสินค้าผ่านผลิตผล () และสินค้าในคลังสินค้าสามารถบริโภคได้ผ่านการบริโภค () การเข้าถึงคลังสินค้าแบบพิเศษร่วมกันผ่านล็อคล็อคพิเศษ: ก่อนที่จะดำเนินการสินค้าในคลังสินค้า (การผลิต/การบริโภค) คลังสินค้าจะถูกล็อคผ่านล็อค () ก่อนจากนั้นปลดล็อคผ่านการปลดล็อค () หลังจากการดำเนินการเสร็จสมบูรณ์
(2) ผู้ผลิตเป็นผู้ผลิต การเรียกฟังก์ชั่นการผลิต () ในผู้ผลิตสามารถสร้างเธรดใหม่เพื่อผลิตผลิตภัณฑ์ในคลังสินค้า
(3) ลูกค้าเป็นหมวดหมู่ผู้บริโภค การเรียกฟังก์ชั่นการบริโภค () ในลูกค้าสามารถสร้างผลิตภัณฑ์การบริโภคเธรดใหม่ในคลังสินค้า
(4) ในหัวข้อหลักหลักเราจะสร้าง MPRO ผู้ผลิตรายใหม่และ MCUs ผู้บริโภคใหม่ พวกเขาผลิต/บริโภคผลิตภัณฑ์ลงในคลังสินค้าตามลำดับ
ตามปริมาณการผลิต/การบริโภคในหลักผลิตภัณฑ์สุดท้ายที่เหลืออยู่ในคลังสินค้าควรเป็น 50 ผลลัพธ์ของการดำเนินการสอดคล้องกับความคาดหวังของเรา!
มีสองปัญหาเกี่ยวกับรุ่นนี้:
(1) ในความเป็นจริงความสามารถของคลังสินค้าไม่สามารถลบได้ อย่างไรก็ตามความสามารถของคลังสินค้าในรุ่นนี้อาจเป็นลบซึ่งขัดแย้งกับความเป็นจริง!
(2) ในความเป็นจริงความสามารถของคลังสินค้ามี จำกัด อย่างไรก็ตามไม่มีขีด จำกัด สำหรับความสามารถในรุ่นนี้จริงๆ!
เราจะพูดคุยสั้น ๆ เกี่ยวกับวิธีการแก้ปัญหาทั้งสองนี้ ทีนี้มาดูตัวอย่างง่ายๆ 2 ก่อน โดยการเปรียบเทียบ "ตัวอย่างที่ 1" และ "ตัวอย่าง 2" เราสามารถเข้าใจวัตถุประสงค์ของการล็อค () และปลดล็อค () ได้ชัดเจนยิ่งขึ้น
2.2 ตัวอย่าง 2
นำเข้า java.util.concurrent.locks.lock; นำเข้า java.util.concurrent.locks.reentrantlock; // locktest2.java// คลังเก็บของที่เก็บ {ขนาด int ส่วนตัว; // จำนวนจริงของล็อคล็อคส่วนตัวที่เก็บ; // คลังสาธารณะล็อคพิเศษ () {this.size = 0; this.lock = ใหม่ reentrantlock (); } โมฆะสาธารณะผลิต (int val) {// lock.lock (); // ลอง {size += val; System.out.printf ("%s ผลิต (%d) -> size =%d/n", thread.currentthread (). getName (), val, ขนาด); //} catch (interruptedException e) {//} ในที่สุด {// lock.unlock (); //}} System.out.printf ("%s กิน (%d) <- size =%d/n", thread.currentthread (). getName (), val, size); //} ในที่สุด {// lock.unlock (); //}}}; // ผู้ผลิตคลาสผู้ผลิต ผู้ผลิตสาธารณะ (คลังเก็บเงิน) {this.depot = เงินฝาก; } // สินค้าอุปโภคบริโภค: สร้างเธรดใหม่เพื่อผลิตผลิตภัณฑ์ลงในคลังสินค้า โมฆะสาธารณะผลิต (สุดท้าย int val) {thread ใหม่ () {public void run () {depent.produce (val); } }.เริ่ม(); }} // ลูกค้าคลาสผู้บริโภค {เงินฝากภาคเอกชน ลูกค้าสาธารณะ (สถานีรถไฟ) {this.depot = เงินฝาก; } // สินค้าอุปโภคบริโภค: สร้างเธรดใหม่เพื่อใช้ผลิตภัณฑ์จากคลังสินค้า การบริโภคโมฆะสาธารณะ (int val สุดท้าย) {thread ใหม่ () {public void run () {depot.consume (val); } }.เริ่ม(); }} คลาสสาธารณะ LockTest2 {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {Depot mdepot = ใหม่สถานีรถไฟ (); ผู้ผลิต mpro = ผู้ผลิตใหม่ (MDEPOT); MCUS ลูกค้า = ลูกค้าใหม่ (MDEPOT); mpro.produce (60); mpro.produce (120); mcus.consume (90); mcus.consume (150); mpro.produce (110); - (ครั้งเดียว) ผลลัพธ์:
Thread-0 ผลิต (60)-> size = -60 ทุ่งหญ้า-4 ผลิต (110)-> size = 50 ทุ่งนา 2 การบริโภค (90) <-size = -60Thread-1 ผลิต (120)-> size = -60Thread-3 การบริโภค (150) <-size = -60
ผลลัพธ์คำอธิบาย:
"ตัวอย่าง 2" ลบล็อคล็อคตาม "ตัวอย่าง 1" ในตัวอย่างที่ 2 ผลิตภัณฑ์สุดท้ายที่เหลืออยู่ในคลังสินค้าคือ -60 ไม่ใช่ 50 ที่เราคาดไว้ เหตุผลก็คือเราไม่ได้ใช้การเข้าถึง Mutex ไปยังที่เก็บ
2.3 ตัวอย่าง 3
ใน "ตัวอย่าง 3" เราใช้เงื่อนไขเพื่อแก้ปัญหาสองอย่างใน "ตัวอย่าง 1": "ความสามารถของคลังสินค้าไม่สามารถเป็นลบ" และ "ความสามารถของคลังสินค้ามี จำกัด "
การแก้ปัญหานี้คือผ่านเงื่อนไข ต้องใช้เงื่อนไขร่วมกับการล็อค: วิธีการรอคอย () ในสภาพสามารถทำให้เธรดถูกบล็อก [คล้ายกับการรอ ()]; วิธีการสัญญาณ () ของเงื่อนไขสามารถทำให้เธรดปลุกไปที่ [คล้ายกับการแจ้งเตือน ()]
นำเข้า java.util.concurrent.locks.lock; นำเข้า java.util.concurrent.locks.reentrantlock; นำเข้า java.util.concurrent.locks.condition; // locktest3.java// คลังสินค้าชั้นเรียน // ความจุของคลังสินค้าขนาด INT ส่วนตัว; // จำนวนคลังสินค้าล็อคส่วนตัวล็อคจริง; // ล็อคแบบพิเศษเงื่อนไข private fullCondition; // เงื่อนไขการผลิตเงื่อนไขส่วนตัว empycondition; // เงื่อนไขการบริโภคสถานีรถไฟสาธารณะ (ความจุ int) {this.capacity = ความจุ; this.size = 0; this.lock = ใหม่ reentrantlock (); this.fullcondtion = lock.newCondition (); this.empycondition = lock.newCondition (); } โมฆะสาธารณะผลิต (int val) {lock.lock (); ลอง {// ซ้ายหมายถึง "ปริมาณที่คุณต้องการผลิต" (อาจเป็นการผลิตมากเกินไปดังนั้นคุณต้องผลิตมากขึ้น) int ซ้าย = val; ในขณะที่ (ซ้าย> 0) {// เมื่อสินค้าคงคลังเต็มให้รอ "ผู้บริโภค" เพื่อบริโภคผลิตภัณฑ์ ในขณะที่ (ขนาด> = ความจุ) fullcondtion.await (); // รับ "ปริมาณการผลิตจริง" (เช่นปริมาณใหม่ที่เพิ่มเข้ามาในสินค้าคงคลัง) // ถ้า "สินค้าคงคลัง" + "ปริมาณการผลิตที่ต้องการ"> "กำลังการผลิตทั้งหมด" จากนั้น "เพิ่มขึ้นจริง" = "กำลังการผลิตทั้งหมด" - "กำลังการผลิตปัจจุบัน" (เติมในคลังสินค้าในเวลานี้) // มิฉะนั้น "เพิ่มขึ้นจริง" = "ปริมาณที่คุณต้องการผลิต" int inc = (ขนาด+ซ้าย)> ความจุ? (ขนาดความจุ): ซ้าย; ขนาด += inc; ซ้าย -= inc; System.out.printf ("%s ผลิต (%3d) -> ซ้าย =%3d, inc =%3d, ขนาด =%3d/n", thread.currentthread (). getName (), val, ซ้าย, inc, ขนาด); // แจ้ง "ผู้บริโภค" ที่คุณสามารถบริโภคได้ empycondtion.signal (); }} catch (interruptedException e) {} ในที่สุด {lock.unlock (); }} การบริโภคโมฆะสาธารณะ (int val) {lock.lock (); ลอง {// ซ้ายหมายถึง "ปริมาณการบริโภคที่จะบริโภค" (อาจมีขนาดใหญ่เกินไปสินค้าคงคลังไม่เพียงพอดังนั้นคุณต้องใช้มากขึ้น) int ซ้าย = val; ในขณะที่ (ซ้าย> 0) {// เมื่อสินค้าคงคลังคือ 0 รอให้ "ผู้ผลิต" ผลิตผลิตภัณฑ์ ในขณะที่ (ขนาด <= 0) empycondtion.await (); // รับ "ปริมาณการบริโภคจริง" (เช่นการลดลงของสินค้าคงคลังที่แท้จริง) // ถ้า "สินค้าคงคลัง" <"ปริมาณที่ลูกค้าต้องการบริโภค" จากนั้น "การบริโภคจริง" = "สินค้าคงคลัง"; // มิฉะนั้น "การบริโภคจริง" = "ปริมาณที่ลูกค้าต้องการบริโภค" int dec = (ขนาด <ซ้าย)? ขนาด: ซ้าย; ขนาด -= ธ.ค. ; ซ้าย -= ธ.ค. ; System.out.printf ("%S กิน (%3D) <- ซ้าย =%3D, DEC =%3D, ขนาด =%3D/N", Thread.currentThread (). getName (), val, ซ้าย, dec, ขนาด); FullCondtion.Signal (); }} catch (interruptedException e) {} ในที่สุด {lock.unlock (); }} สตริงสาธารณะ toString () {return "ความจุ:"+ความจุ+"ขนาดจริง:"+ขนาด; }}; // ผู้ผลิตคลาสผู้ผลิต {เงินฝากภาคเอกชน ผู้ผลิตสาธารณะ (คลังเก็บเงิน) {this.depot = เงินฝาก; } // สินค้าอุปโภคบริโภค: สร้างเธรดใหม่เพื่อผลิตผลิตภัณฑ์ลงในคลังสินค้า โมฆะสาธารณะผลิต (สุดท้าย int val) {thread ใหม่ () {public void run () {depent.produce (val); } }.เริ่ม(); }} // ลูกค้าคลาสผู้บริโภค {เงินฝากภาคเอกชน ลูกค้าสาธารณะ (สถานีรถไฟ) {this.depot = เงินฝาก; } // สินค้าอุปโภคบริโภค: สร้างเธรดใหม่เพื่อใช้ผลิตภัณฑ์จากคลังสินค้า การบริโภคโมฆะสาธารณะ (int val สุดท้าย) {thread ใหม่ () {public void run () {depot.consume (val); } }.เริ่ม(); }} คลาสสาธารณะ LockTest3 {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {Depot mdepot = ใหม่สถานีรถไฟ (100); ผู้ผลิต mpro = ผู้ผลิตใหม่ (MDEPOT); MCUS ลูกค้า = ลูกค้าใหม่ (MDEPOT); mpro.produce (60); mpro.produce (120); mcus.consume (90); mcus.consume (150); mpro.produce (110); - (ครั้งเดียว) ผลลัพธ์:
Thread-0 ผลิต (60)-> ซ้าย = 0, inc = 60, size = 60Thread-1 ผลิต (120)-> ซ้าย = 80, inc = 40, size = 100 ทเธด 2 การบริโภค (90) <-ซ้าย = 0, ธ.ค. = 90, ขนาด = 10thread-3 การบริโภค (150) <-ซ้าย = 40, ธ.ค. = 100, size = 0thread-4 ผลิต (110)-> ซ้าย = 0, inc = 10, size = 10 Thread-3 บริโภค (150) <-ซ้าย = 30, ธ.ค. = 10, ขนาด = 0thread-1 ผลิต (120)-> ซ้าย = 0, inc = 80, ขนาด = 80thread