ก่อนที่จะมี Java 5.0 กลไกเดียวที่สามารถใช้ในการประสานงานการเข้าถึงวัตถุที่ใช้ร่วมกันนั้นถูกซิงโครไนซ์และผันผวน เรารู้ว่าคำหลักที่ซิงโครไนซ์ใช้การล็อคในตัวในขณะที่คำหลักที่ผันผวนช่วยให้มั่นใจได้ว่าการมองเห็นหน่วยความจำสำหรับมัลติเธรด ในกรณีส่วนใหญ่กลไกเหล่านี้สามารถทำงานได้ดี แต่พวกเขาไม่สามารถใช้งานฟังก์ชั่นขั้นสูงเพิ่มเติมได้เช่นการไม่สามารถขัดจังหวะเธรดที่รอการล็อคไม่สามารถใช้กลไกล็อคที่ จำกัด เวลาได้โดยไม่สามารถใช้กฎล็อคสำหรับโครงสร้างที่ไม่ปิดกั้น ฯลฯ ดังนั้นจึงมีการเพิ่มกลไกใหม่ใน Java 5.0: Reentrantlock คลาส Reentrantlock ใช้อินเทอร์เฟซล็อคและให้การมองเห็น mutex และหน่วยความจำเดียวกันกับการซิงโครไนซ์ เลเยอร์พื้นฐานของมันคือการบรรลุการซิงโครไนซ์แบบมัลติเธรดผ่าน AQS เมื่อเปรียบเทียบกับการล็อคในตัว Reentrantlock ไม่เพียง แต่ให้กลไกการล็อคที่สมบูรณ์ยิ่งขึ้น แต่ยังไม่ด้อยกว่าการล็อคในตัวในประสิทธิภาพ (ดีกว่าล็อคในตัวในรุ่นก่อนหน้า) หลังจากได้พูดคุยเกี่ยวกับข้อได้เปรียบมากมายของ reentrantlock ให้ค้นพบซอร์สโค้ดและดูการใช้งานเฉพาะ
1. คำแนะนำเบื้องต้นเกี่ยวกับคำหลักที่ซิงโครไนซ์
Java ให้บริการล็อคในตัวเพื่อรองรับการซิงโครไนซ์แบบมัลติเธรด JVM ระบุบล็อกรหัสที่ซิงโครไนซ์ตามคำหลักที่ซิงโครไนซ์ เมื่อเธรดเข้าสู่บล็อกรหัสที่ซิงโครไนซ์จะได้รับการล็อคโดยอัตโนมัติ เมื่อออกจากบล็อกรหัสที่ซิงโครไนซ์ล็อคจะถูกปล่อยออกมาโดยอัตโนมัติ หลังจากหนึ่งเธรดได้รับล็อคเธรดอื่นจะถูกบล็อก แต่ละวัตถุ Java สามารถใช้เป็นล็อคที่ใช้การซิงโครไนซ์ คำหลักที่ซิงโครไนซ์สามารถใช้ในการแก้ไขวิธีการของวัตถุวิธีการคงที่และบล็อกรหัส เมื่อแก้ไขวิธีการของวัตถุและวิธีการคงที่ล็อคคือวัตถุที่วิธีการอยู่และวัตถุคลาส เมื่อแก้ไขบล็อกรหัสจะต้องให้วัตถุเพิ่มเติมเป็นล็อค เหตุผลที่แต่ละวัตถุ Java สามารถใช้เป็นล็อคได้คือวัตถุตรวจสอบ (การจัดการ) มีความเกี่ยวข้องในส่วนหัวของวัตถุ เมื่อเธรดเข้าสู่บล็อกรหัสแบบซิงโครนัสมันจะถือวัตถุมอนิเตอร์โดยอัตโนมัติและเมื่อออกจะออกมันจะปล่อยวัตถุมอนิเตอร์โดยอัตโนมัติ เมื่อวัตถุตรวจสอบถูกเก็บไว้เธรดอื่นจะถูกบล็อก แน่นอนว่าการดำเนินการซิงโครไนซ์เหล่านี้ดำเนินการโดยเลเยอร์พื้นฐาน JVM แต่ยังมีความแตกต่างบางประการในการใช้งานพื้นฐานของวิธีการปรับเปลี่ยนคำหลักที่ซิงโครไนซ์และบล็อกรหัส วิธีการปรับเปลี่ยนคำหลักที่ซิงโครไนซ์นั้นถูกซิงโครไนซ์โดยปริยายนั่นคือไม่จำเป็นต้องควบคุมผ่านคำสั่ง bytecode JVM สามารถแยกแยะว่าวิธีการเป็นวิธีที่ซิงโครไนซ์ตามค่าสถานะการเข้าถึง ACC_Synchronized ในตารางวิธีการหรือไม่ ในขณะที่บล็อกรหัสที่แก้ไขโดยคำหลักที่ซิงโครไนซ์จะถูกซิงโครไนซ์อย่างชัดเจนซึ่งควบคุมการถือครองของเธรดและการปล่อยไปป์ไลน์ผ่านคำแนะนำของ MonitorEnenter และ Monitorexit bytecode วัตถุตรวจสอบมีฟิลด์ _count ภายใน _Count เท่ากับ 0 หมายความว่าไม่มีการจัดท่อและ _Count มากกว่า 0 หมายความว่าท่อถูกเก็บไว้ ทุกครั้งที่เธรดการถือครองกลับมาใหม่ _count จะถูกเพิ่ม 1 และทุกครั้งที่ออกจากเธรดการถือครอง _count จะลดลง 1 นี่คือหลักการดำเนินการของการล็อคในตัว นอกจากนี้ยังมีสองคิวภายในวัตถุจอภาพ _entrylist และ _waitset ซึ่งสอดคล้องกับคิวการซิงโครไนซ์และคิวแบบมีเงื่อนไขของ AQS เมื่อเธรดล้มเหลวในการรับล็อคมันจะบล็อกใน _entrylist เมื่อมีการเรียกวิธีการรอของวัตถุล็อคเธรดจะป้อน _waitset เพื่อรอ นี่คือหลักการใช้งานของการซิงโครไนซ์เธรดและการรอเงื่อนไขสำหรับการล็อคในตัว
2. การเปรียบเทียบระหว่าง reentrantlock และซิงโครไนซ์
คำหลักที่ซิงโครไนซ์เป็นกลไกการล็อคในตัวที่จัดทำโดย Java การดำเนินการซิงโครไนซ์นั้นดำเนินการโดย JVM พื้นฐาน Reentrantlock เป็นล็อคที่ชัดเจนโดยแพ็คเกจ java.util.concurrent และการดำเนินการซิงโครไนซ์นั้นใช้พลังงานจาก AQS synchronizer Reentrantlock ให้ความหมายเดียวกันเกี่ยวกับการล็อคและหน่วยความจำเป็นล็อคในตัวนอกจากนี้ยังมีคุณสมบัติอื่น ๆ บางอย่างรวมถึงการรอล็อคเวลาการรอล็อคแบบขัดจังหวะการล็อคที่เป็นธรรมและการล็อคโครงสร้างที่ไม่ล็อค นอกจากนี้ Reentrantlock ยังมีข้อได้เปรียบด้านประสิทธิภาพบางอย่างในรุ่น JDK รุ่นแรก เนื่องจาก Reentrantlock มีข้อได้เปรียบมากมายทำไมเราควรใช้คำหลักที่ซิงโครไนซ์ ในความเป็นจริงหลายคนใช้ reentrantlock เพื่อแทนที่การทำงานล็อคของคำหลักที่ซิงโครไนซ์ อย่างไรก็ตามล็อคในตัวยังคงมีข้อได้เปรียบของตัวเอง ล็อคในตัวมีความคุ้นเคยกับนักพัฒนาหลายคนและใช้งานง่ายและมีขนาดกะทัดรัดมากขึ้น เนื่องจากล็อคที่ชัดเจนจะต้องเรียกว่าปลดล็อคด้วยตนเองในบล็อกในที่สุดจึงค่อนข้างปลอดภัยที่จะใช้ล็อคในตัว ในขณะเดียวกันก็มีแนวโน้มที่จะปรับปรุงประสิทธิภาพของการซิงโครไนซ์มากกว่า reentrantlock ในอนาคต เนื่องจากการซิงโครไนซ์เป็นคุณสมบัติในตัวของ JVM จึงสามารถทำการปรับให้เหมาะสมบางอย่างเช่นการเพิ่มประสิทธิภาพการกำจัดล็อคสำหรับวัตถุล็อคที่ปิดล้อมเธรดการกำจัดการซิงโครไนซ์ของล็อคในตัวโดยการเพิ่มความละเอียดของล็อคและหากฟังก์ชั่นเหล่านี้ถูกนำไปใช้ ดังนั้นเมื่อจำเป็นต้องใช้คุณสมบัติขั้นสูงบางอย่างควรใช้ reentrantlock ซึ่งรวมถึง: การดำเนินงานการล็อคล็อคที่กำหนดเวลาสำรวจและขัดจังหวะการล็อคคิวคิวที่เป็นธรรมและล็อคโครงสร้างที่ไม่ใช่บล็อก มิฉะนั้นควรใช้การซิงโครไนซ์ก่อน
3. การดำเนินงานของการรับและปล่อยล็อค
ก่อนอื่นให้ดูที่รหัสตัวอย่างโดยใช้ Reentrantlock เพื่อเพิ่มการล็อค
โมฆะสาธารณะ dosomething () {// ค่าเริ่มต้นคือการได้รับล็อคที่ไม่ใช่ล็อค reentrantlock ล็อค = ใหม่ reentrantlock (); ลอง {// lock lock.lock () ก่อนดำเนินการ; // ดำเนินการดำเนินการ ... } ในที่สุด {// ล็อคล็อค. unlock () ในที่สุดก็ปล่อย; -ต่อไปนี้เป็น API สำหรับการรับและปล่อยล็อค
// การดำเนินการของการล็อคโมฆะสาธารณะล็อคล็อค () {sync.lock ();} // การทำงานของการปลดล็อคโมฆะสาธารณะปลดล็อค () {sync.release (1);};คุณจะเห็นได้ว่าการดำเนินการล็อคและปล่อยล็อคจะถูกมอบหมายให้กับวิธีการล็อคและวิธีการปล่อยของวัตถุซิงค์ตามลำดับ
ชั้นเรียนสาธารณะ Reentrantlock ใช้ Lock, java.io.serializable {ส่วนตัวซิงค์ซิงค์ครั้งสุดท้ายส่วนตัว; บทคัดย่อคลาสคงที่การซิงค์จะขยาย AbstractqueuedSynchronizer {Abstract Void Lock (); } // synchronizer ที่ใช้การล็อคที่ไม่ใช่แบบคงที่ระดับสุดท้าย nonfairsync ขยายการซิงค์ {void lock สุดท้าย () {... }}} // synchronizer ที่ใช้การล็อคแบบคงที่ Fairsync Fairsyncวัตถุ Reentrantlock แต่ละชิ้นมีการอ้างอิงของประเภทการซิงค์ คลาสการซิงค์นี้เป็นคลาสภายในที่เป็นนามธรรม มันสืบทอดมาจาก AbstractqueuedSynchronizer วิธีการล็อคภายในเป็นวิธีนามธรรม การซิงค์ตัวแปรสมาชิกของ Reentrantlock ได้รับการกำหนดค่าในระหว่างการก่อสร้าง ลองมาดูกันว่าวิธีการสร้างใหม่ของ Reentrantlock ทำอะไรกันบ้าง?
// ตัวสร้างพารามิเตอร์เริ่มต้นสาธารณะ reentrantlock () {sync = ใหม่ nonfairsync ();} // ตัวสร้างพารามิเตอร์สาธารณะ reentrantlock (Boolean Fair) {sync = fair? ใหม่ fairsync (): nonfairsync () ใหม่};การเรียกตัวสร้างพารามิเตอร์เริ่มต้นจะกำหนดอินสแตนซ์ nonfairsync ให้ซิงค์และล็อคเป็นล็อคที่ไม่ใช่คู่ในเวลานี้ ตัวสร้างพารามิเตอร์อนุญาตให้พารามิเตอร์ระบุว่าจะกำหนดอินสแตนซ์ fairsync หรืออินสแตนซ์ nonfairsync เพื่อซิงค์ Nonfairsync และ Fairsync ทั้งคู่สืบทอดมาจากคลาส SYNC และเขียนใหม่วิธีการล็อค () ดังนั้นจึงมีความแตกต่างระหว่างล็อคที่เป็นธรรมและล็อคที่ไม่ใช่คู่ในการรับล็อคซึ่งเราจะพูดถึงด้านล่าง มาดูการดำเนินการเพื่อปล่อยล็อค ทุกครั้งที่คุณเรียกวิธีการปลดล็อค () คุณเพียงแค่ดำเนินการ sync.release (1) การดำเนินการ การดำเนินการนี้จะเรียกวิธีการเปิดตัว () ของคลาส AbstractqueuedSynchronizer ลองตรวจสอบอีกครั้ง
// ปล่อยการดำเนินการล็อค (โหมดพิเศษ) การเปิดตัวบูลีนสุดท้าย (int arg) {// หมุนรหัสผ่านล็อคเพื่อดูว่าสามารถปลดล็อคได้หรือไม่ถ้า (tryrelease (arg)) {// รับโหนดโหนดหัว h = head; // หากโหนดหัวไม่ว่างเปล่าและสถานะการรอไม่เท่ากับ 0 ปลุกโหนดผู้สืบทอดถ้า (h! = null && h.waitstatus! = 0) {// ปลุกโหนดผู้สืบทอด Unparksuccessor (H); } return true; } return false;}วิธีการวางจำหน่ายนี้เป็น API สำหรับการปล่อยการดำเนินการล็อคที่จัดทำโดย AQS ก่อนอื่นเรียกวิธี tryrelease เพื่อพยายามรับการล็อค วิธี tryRelease เป็นวิธีนามธรรมและตรรกะการใช้งานอยู่ในการซิงค์คลาสย่อย
// พยายามที่จะปล่อยล็อคที่ได้รับการป้องกันขั้นสุดท้าย boolean tryrelease (releases int) {int c = getState () - เผยแพร่; // ถ้าเธรดที่ถือล็อคไม่ใช่เธรดปัจจุบันข้อยกเว้นจะถูกโยนลงถ้า (thread.currentthread ()! = getExClusiveOwnerThread ()) {โยนใหม่ lelveralMonitorStateException (); } บูลีนฟรี = false; // หากสถานะการซิงโครไนซ์เป็น 0 นั่นหมายความว่าการล็อคจะถูกปล่อยออกมาหาก (C == 0) {// ตั้งค่าสถานะของล็อคที่ถูกปล่อยออกมาเป็นจริง = จริง; // ตั้งค่าเธรดที่ถูกครอบครองเป็น setExClusiveOwnerThread (NULL) ที่ว่างเปล่า; } setState (c); คืนฟรี;}วิธีการ tryrelease นี้จะได้รับสถานะการซิงโครไนซ์ปัจจุบันก่อนลบสถานะการซิงโครไนซ์ปัจจุบันจากพารามิเตอร์ที่ส่งผ่านไปยังสถานะการซิงโครไนซ์ใหม่แล้วพิจารณาว่าสถานะการซิงโครไนซ์ใหม่เท่ากับ 0 ถ้าเท่ากับ 0 หมายความว่าล็อคปัจจุบันถูกปล่อยออกมา จากนั้นตั้งค่าสถานะการเปิดตัวของการล็อคเป็น TRUE จากนั้นล้างเธรดที่อยู่ในปัจจุบันและในที่สุดก็เรียกใช้เมธอด SetState เพื่อตั้งค่าสถานะการซิงโครไนซ์ใหม่และส่งคืนสถานะการเปิดตัวของล็อค
4. ล็อคอย่างยุติธรรมและล็อคที่ไม่เป็นธรรม
เรารู้ว่าอินสแตนซ์เฉพาะคือ reentrantlock ที่ชี้ไปที่การซิงค์ ในระหว่างการก่อสร้างจะมีการกำหนดตัวแปรตัวแปรสมาชิก หากค่าถูกกำหนดให้กับอินสแตนซ์ nonfairsync นั่นหมายความว่ามันเป็นล็อคที่ไม่ใช่ค่าธรรมเนียมและหากค่าถูกกำหนดให้กับอินสแตนซ์ fairsync ก็หมายความว่ามันเป็นล็อคที่ยุติธรรม หากเป็นล็อคที่ยุติธรรมเธรดจะได้รับการล็อคตามลำดับที่พวกเขาทำคำขอ แต่ในการล็อคที่ไม่เป็นธรรมพฤติกรรมการตัดจะได้รับอนุญาต: เมื่อเธรดร้องขอล็อคที่ไม่เป็นธรรมหากสถานะของล็อคจะพร้อมใช้งานในเวลาเดียวกัน มาดูวิธีการล็อคที่ไม่เป็นธรรม
// synchronizer ที่ไม่เป็นธรรมระดับสุดท้าย nonfairsync ขยายการซิงค์ {// ใช้วิธีนามธรรมของคลาสพาเรนต์เพื่อรับการล็อคสุดท้ายล็อคล็อคสุดท้าย () {// ใช้เมธอด CAS เพื่อตั้งค่าสถานะการซิงโครไนซ์ถ้า (เปรียบเทียบ SetExCLANGENDINGENDIND. ) {// // ถ้าการตั้งค่า } else {// มิฉะนั้นหมายความว่าการล็อคถูกครอบครองแล้วการโทรหาและปล่อยให้เธรดเข้าคิวเพื่อซิงโครไนซ์คิวเพื่อรับ (1); }} // วิธีที่จะพยายามรับการล็อคการป้องกันขั้นสุดท้ายบูลีน tryacquire (int ซื้อ) {ส่งคืน nonfirtryAcquire (ซื้อ); }} // ซื้อล็อคในโหมดที่ไม่เกิดการไม่ผ่าน (โหมดพิเศษ) โมฆะสุดท้ายของสาธารณะได้รับ (int arg) {ถ้า (! tryacquire (arg) && acquirequeued (addwaiter (node.exclusive), arg)) {selfinterrupt (); -จะเห็นได้ว่าในวิธีการล็อคของการล็อคที่ไม่เป็นธรรมเธรดจะเปลี่ยนค่าของสถานะการซิงโครไนซ์จาก 0 ถึง 1 ในขั้นตอนแรกใน CAS ในความเป็นจริงการดำเนินการนี้เทียบเท่ากับการพยายามรับการล็อค หากการเปลี่ยนแปลงประสบความสำเร็จหมายความว่าเธรดได้รับการล็อคตอนนี้และไม่จำเป็นต้องคิวในคิวการซิงโครไนซ์อีกต่อไป หากการเปลี่ยนแปลงล้มเหลวหมายความว่าการล็อคยังไม่ได้รับการปล่อยตัวเมื่อเธรดมาเป็นครั้งแรกดังนั้นวิธีการรับจะเรียกว่าถัดไป เรารู้ว่าวิธีการที่ได้รับนี้สืบทอดมาจากวิธี AbstractqueuedSynchronizer มาตรวจสอบวิธีนี้กันเถอะ หลังจากเธรดเข้าสู่วิธีการรับวิธีแรกการโทรวิธี Tryacquire เพื่อพยายามรับการล็อค เนื่องจาก nonfairsync เขียนทับวิธี Tryacquire และเรียกใช้วิธี NonfirtryCquire ของการซิงค์คลาสแม่ในวิธีการวิธีการที่ไม่ได้รับการจัดการจะถูกเรียกที่นี่เพื่อพยายามรับการล็อค มาดูกันว่าวิธีนี้ทำอย่างไรโดยเฉพาะ
// การเข้าซื้อกิจการที่ไม่เป็นธรรมของ Lock Final Boolean NonfirtryAcquire (Int Acquires) {// รับเธรดปัจจุบันกระแสสุดท้ายกระแสไฟฟ้าปัจจุบัน = thread.currentThread (); // รับสถานะการซิงโครไนซ์ปัจจุบัน int c = getState (); // ถ้าสถานะการซิงโครไนซ์เป็น 0 นั่นหมายความว่าการล็อคไม่ได้ถูกครอบครองถ้า (c == 0) {// ใช้ CAS เพื่ออัปเดตสถานะการซิงโครไนซ์ถ้า (เปรียบเทียบกับ (0, 0, ซื้อ)) {// ตั้งเธรดที่อยู่ในปัจจุบัน กลับมาจริง; } // มิฉะนั้นจะถูกกำหนดว่าล็อคเป็นเธรดปัจจุบัน} อื่นถ้า (current == getExClusiveOwnerThread ()) {// ถ้าล็อคถูกเก็บไว้โดยเธรดปัจจุบันให้แก้ไขสถานะการซิงโครไนซ์ปัจจุบันโดยตรง if (nextc <0) {โยนข้อผิดพลาดใหม่ ("จำนวนล็อคสูงสุดเกิน"); } setState (nextc); กลับมาจริง; } // หากล็อคไม่ใช่เธรดปัจจุบันให้ส่งคืนธงล้มเหลวส่งคืนเท็จ;}วิธี NonfirtryAcquire เป็นวิธีการซิงค์ เราจะเห็นได้ว่าหลังจากเธรดเข้าสู่วิธีนี้แล้วมันจะได้รับสถานะการซิงโครไนซ์ก่อน หากสถานะการซิงโครไนซ์เป็น 0 ให้ใช้การดำเนินการ CAS เพื่อเปลี่ยนสถานะการซิงโครไนซ์ อันที่จริงนี่คือการได้รับล็อคอีกครั้ง หากสถานะการซิงโครไนซ์ไม่ใช่ 0 นั่นหมายความว่าล็อคถูกครอบครอง ในเวลานี้เราจะพิจารณาก่อนว่าเธรดที่ถือล็อคเป็นเธรดปัจจุบันหรือไม่ ถ้าเป็นเช่นนั้นสถานะการซิงโครไนซ์จะเพิ่มขึ้น 1 มิฉะนั้นการดำเนินการของการพยายามที่จะได้รับการล็อคจะล้มเหลว ดังนั้นวิธี AddWaiter จะถูกเรียกให้เพิ่มเธรดไปยังคิวการซิงโครไนซ์ ในการสรุปในโหมดล็อคที่ไม่เป็นธรรมเธรดจะพยายามรับล็อคสองตัวก่อนที่จะเข้าสู่คิวการซิงโครไนซ์ หากการได้มานั้นสำเร็จจะไม่เข้าสู่คิวคิวคิวคิวคิวการซิงโครไนซ์มิฉะนั้นจะเข้าสู่คิวคิวคิวคิวการซิงโครไนซ์คิว ถัดไปมาดูวิธีรับล็อคที่เป็นธรรม
// synchronizer ที่ใช้งาน Fair Lock Final Class สุดท้าย FairSync ขยายการซิงค์ {// ใช้วิธีนามธรรมของคลาสพาเรนต์เพื่อรับการล็อคขั้นสุดท้ายล็อคโมฆะ () {// การโทรหาที่ได้มาและปล่อยให้เธรดคิวเพื่อซิงโครไนซ์คิวเพื่อรับ (1) } // ลองรับการล็อคการป้องกันขั้นสุดท้ายบูลีน tryacQuire (ซื้อ int) {// รับเธรดปัจจุบันกระแสสุดท้ายเธรดปัจจุบัน = thead.currentThread (); // รับสถานะการซิงโครไนซ์ปัจจุบัน int c = getState (); // ถ้าสถานะการซิงโครไนซ์ 0 หมายความว่าการล็อคไม่ได้ถูกครอบครองถ้า (c == 0) {// ปกป้องว่ามีโหนดไปข้างหน้าในคิวการซิงโครไนซ์หรือไม่ถ้า (! กลับมาจริง; } // มิฉะนั้นให้ตรวจสอบว่าเธรดปัจจุบันถือล็อค} อื่นถ้า (current == getExClusiveOwnerThread ()) {// ถ้าเธรดปัจจุบันถือล็อคจะแก้ไขสถานะการซิงโครไนซ์โดยตรง int nextc = c + ที่ได้มา; if (nextc <0) {โยนข้อผิดพลาดใหม่ ("จำนวนล็อคสูงสุดเกิน"); } setState (nextc); กลับมาจริง; } // หากเธรดปัจจุบันไม่ถือล็อคการได้มาจะไม่ส่งคืนเท็จ - เมื่อเรียกวิธีการล็อคของ Fair Lock วิธีการรับจะถูกเรียกโดยตรง ในทำนองเดียวกันวิธีการซื้อครั้งแรกเรียกวิธีการเขียน fairsync rewrite tryacquire เพื่อพยายามรับการล็อค ในวิธีนี้ค่าของสถานะการซิงโครไนซ์จะได้รับก่อน หากสถานะการซิงโครไนซ์เป็น 0 หมายความว่าการล็อคจะถูกปล่อยออกมาในเวลานี้ ความแตกต่างจากการล็อคที่ไม่เป็นธรรมคือมันจะเรียกวิธีการที่ HasqueuedPredpredeades เพื่อตรวจสอบว่ามีคนเข้าคิวในคิวการซิงโครไนซ์หรือไม่ หากไม่มีใครเข้าคิวค่าของสถานะการซิงโครไนซ์จะถูกแก้ไข คุณจะเห็นได้ว่าการล็อคที่ยุติธรรมใช้วิธีการที่ได้รับความอนุเคราะห์ที่นี่แทนที่จะได้รับล็อคทันที ยกเว้นขั้นตอนนี้ที่แตกต่างจากการล็อคที่ไม่เป็นธรรมการดำเนินการอื่น ๆ จะเหมือนกัน เพื่อสรุปเราจะเห็นว่าล็อคที่เป็นธรรมจะตรวจสอบสถานะของการล็อคหนึ่งครั้งก่อนที่จะเข้าสู่คิวการซิงโครไนซ์ แม้ว่าคุณจะพบว่าล็อคเปิดอยู่คุณก็จะไม่ได้รับทันที แต่คุณจะปล่อยให้เธรดในคิวการซิงโครไนซ์ได้มาก่อน ดังนั้นจึงสามารถมั่นใจได้ว่าคำสั่งที่เธรดทั้งหมดได้รับล็อคภายใต้การล็อคอย่างยุติธรรมเป็นครั้งแรกและจากนั้นก็มาถึงซึ่งทำให้มั่นใจได้ถึงความเป็นธรรมในการได้รับล็อค
แล้วทำไมเราไม่ต้องการให้ล็อคทั้งหมดมีความยุติธรรม? ท้ายที่สุดความยุติธรรมเป็นพฤติกรรมที่ดีและความไม่ยุติธรรมเป็นพฤติกรรมที่ไม่ดี เนื่องจากการดำเนินการระงับและการปลุกของเธรดมีค่าใช้จ่ายขนาดใหญ่จึงส่งผลกระทบต่อประสิทธิภาพของระบบโดยเฉพาะอย่างยิ่งในกรณีของการแข่งขันที่รุนแรงล็อคที่เป็นธรรมจะนำไปสู่การระงับและตื่นขึ้นมาของเธรดที่ไม่ได้เป็นจริง นอกจากนี้เนื่องจากเธรดส่วนใหญ่ใช้ล็อคในช่วงเวลาสั้น ๆ และการดำเนินการปลุกของเธรดจะมีความล่าช้าจึงเป็นไปได้ที่เธรด B จะได้รับการล็อคทันทีและปล่อยล็อคหลังจากใช้งาน สิ่งนี้นำไปสู่สถานการณ์ที่ชนะ ช่วงเวลาที่เธรด A ที่ได้มาล็อคจะไม่ล่าช้า แต่เธรด B ใช้ล็อคล่วงหน้าและปริมาณงานได้รับการปรับปรุงเช่นกัน
5. กลไกการใช้งานของคิวแบบมีเงื่อนไข
มีข้อบกพร่องบางอย่างในคิวเงื่อนไขในตัว ล็อคในตัวแต่ละตัวสามารถมีคิวเงื่อนไขที่เกี่ยวข้องเพียงหนึ่งเดียวเท่านั้นซึ่งทำให้หลายเธรดรอการประชุมสภาพที่แตกต่างกันในคิวเงื่อนไขเดียวกัน จากนั้นทุกครั้งที่มีการแจ้งให้ทราบว่าจะมีการปลุกเธรดที่รอทั้งหมด เมื่อเธรดตื่นขึ้นมาพบว่ามันไม่ใช่เงื่อนไขที่จะรอคอยและจะถูกระงับ สิ่งนี้นำไปสู่การปลุกของเธรดที่ไร้ประโยชน์จำนวนมากและระงับการดำเนินงานซึ่งจะเสียทรัพยากรระบบจำนวนมากและลดประสิทธิภาพของระบบ หากคุณต้องการเขียนวัตถุที่เกิดขึ้นพร้อมกันที่มีเพรดิเคตแบบมีเงื่อนไขหลายรายการหรือหากคุณต้องการควบคุมมากกว่าการมองเห็นคิวแบบมีเงื่อนไขคุณต้องใช้ล็อคและเงื่อนไขที่ชัดเจนแทนที่จะเป็นล็อคในตัวและคิวแบบมีเงื่อนไข เงื่อนไขและล็อคเชื่อมโยงกันเช่นคิวเงื่อนไขและล็อคในตัว ในการสร้างเงื่อนไขคุณสามารถโทรหาวิธีล็อคนิวคอนชั่นบนล็อคที่เกี่ยวข้อง ก่อนอื่นให้ดูตัวอย่างโดยใช้เงื่อนไข
คลาสสาธารณะ BoundedBuffer {ล็อคสุดท้ายล็อค = ใหม่ reentrantlock (); เงื่อนไขสุดท้าย notfull = lock.newCondition (); // เงื่อนไข predicate: notfull เงื่อนไขสุดท้าย notempty = lock.newCondition (); // เงื่อนไข predicate: notempty final object [] item = วัตถุใหม่ [100]; int putptr, takeptr, count; // วิธีการผลิตโมฆะสาธารณะใส่ (วัตถุ x) พ่น InterruptedException {lock.lock (); ลอง {ในขณะที่ (count == items.length) notfull.await (); // คิวเต็มและเธรดกำลังรอรายการ [putptr] บนคิว Notfull รายการ [putptr] = x; if (++ putptr == items.length) putptr = 0; ++ นับ; notempty.signal (); // การผลิตประสบความสำเร็จปลุกโหนดของคิว notempty} ในที่สุด {lock.unlock (); }} // วิธีการบริโภควัตถุสาธารณะใช้ () พ่น InterruptedException {lock.lock (); ลอง {ในขณะที่ (count == 0) notempty.await (); // คิวว่างเปล่าเธรดรอสำหรับวัตถุ x = รายการ [takeptr] บนคิว notempty; if (++ takeptr == items.length) takeptr = 0; --นับ; notfull.signal (); // การบริโภคประสบความสำเร็จปลุกโหนดของการส่งคืนคิว notfull return x; } ในที่สุด {lock.unlock (); -วัตถุล็อคสามารถสร้างคิวเงื่อนไขได้หลายคิวและคิวเงื่อนไขสองคิวถูกสร้างขึ้นที่นี่เป็นสิ่งที่น่าสนใจและเป็นไปไม่ได้ เมื่อคอนเทนเนอร์เต็มเธรดที่เรียกวิธีการใส่จะต้องถูกบล็อก รอจนกว่าเงื่อนไขจะเป็นจริง (คอนเทนเนอร์ไม่พอใจ) ตื่นขึ้นมาและดำเนินการต่อไป เมื่อคอนเทนเนอร์ว่างเปล่าเธรดที่เรียกใช้วิธีการใช้จะต้องถูกบล็อก รอจนกว่าเงื่อนไขจะเป็นจริง (คอนเทนเนอร์ไม่ว่างเปล่า) ตื่นขึ้นมาและดำเนินการต่อไป เธรดสองประเภทนี้รอตามเงื่อนไขที่แตกต่างกันดังนั้นพวกเขาจะป้อนคิวเงื่อนไขสองคิวที่แตกต่างกันเพื่อบล็อกและรอจนถึงเวลาที่เหมาะสมก่อนที่จะตื่นขึ้นมาโดยเรียก API บนวัตถุเงื่อนไข ต่อไปนี้เป็นรหัสการใช้งานของวิธีการปรับสภาพใหม่
// สร้างเงื่อนไขคิวสภาพสาธารณะเงื่อนไขใหม่ () {return sync.newCondition ();} การซิงค์คลาสแบบคงที่บทคัดย่อจะขยาย Abstractqueuedsynchronizer {// สร้างเงื่อนไขใหม่วัตถุสุดท้ายเงื่อนไขการปรับสภาพใหม่ () {ส่งคืนเงื่อนไขใหม่ (); -การใช้คิวเงื่อนไขบน reentrantlock ขึ้นอยู่กับ Abstractqueuedsynchronizer วัตถุเงื่อนไขที่เราได้รับเมื่อเรียกใช้วิธีการปรับเปลี่ยนใหม่เป็นอินสแตนซ์ของเงื่อนไขคลาสภายในของ AQS การดำเนินการทั้งหมดในคิวเงื่อนไขทำได้โดยเรียก API ที่จัดทำโดย ConditionObject สำหรับการดำเนินการตามเงื่อนไขเฉพาะของเงื่อนไขคุณสามารถตรวจสอบบทความของฉัน "ชุดพร้อมกันของ Java [4] ----- AbstractqueuedSynchronizer การวิเคราะห์รหัสที่มาตามเงื่อนไขคิวเงื่อนไข" และฉันจะไม่ทำซ้ำที่นี่ ณ จุดนี้การวิเคราะห์ซอร์สโค้ดของเราของ Reentrantlock สิ้นสุดลงแล้ว ฉันหวังว่าการอ่านบทความนี้จะช่วยให้ผู้อ่านเข้าใจและ Master Reentrantlock
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น