ผ่านการวิเคราะห์บทความสามบทความก่อนหน้านี้เรามีความเข้าใจอย่างลึกซึ้งเกี่ยวกับโครงสร้างภายในและแนวคิดการออกแบบบางอย่างของ AbstractqueuedSynchronizer และเรารู้ว่า AbstractqueuedSynchronizer รักษาสถานะการซิงโครไนซ์และพื้นที่คิวสองแห่งซึ่งเป็นคิวแบบซิงโครนัสและคิวตามเงื่อนไขตามลำดับ มาใช้ห้องน้ำสาธารณะเป็นการเปรียบเทียบ คิวการซิงโครไนซ์เป็นพื้นที่คิวหลัก หากห้องน้ำสาธารณะไม่เปิดทุกคนที่ต้องการเข้าห้องน้ำจะต้องเข้าแถวที่นี่ คิวเงื่อนไขส่วนใหญ่ตั้งไว้สำหรับการรอเงื่อนไข ลองจินตนาการว่าหากคน ๆ หนึ่งประสบความสำเร็จในการล็อคและเข้าห้องน้ำผ่านคิว แต่พบว่าเขาไม่ได้นำกระดาษชำระก่อนความสะดวก แม้ว่าเขาจะทำอะไรไม่ถูกเมื่อพบกับสถานการณ์นี้ แต่ก็ต้องยอมรับความจริงนี้ด้วย ในเวลานี้มันต้องออกไปข้างนอกและเตรียมกระดาษชำระก่อน (ป้อนคิวเงื่อนไขเพื่อรอ) แน่นอนก่อนออกไปล็อคจะต้องได้รับการปล่อยตัวเพื่อให้คนอื่น ๆ สามารถเข้ามาหลังจากเตรียมกระดาษชำระ (ตามเงื่อนไข) จะต้องกลับไปที่คิวซิงโครนัสเพื่อคิวอีกครั้ง แน่นอนว่าไม่ใช่ทุกคนที่เข้ามาในห้องไม่ได้นำกระดาษชำระ อาจมีเหตุผลอื่น ๆ ที่พวกเขาจะต้องขัดจังหวะการดำเนินการและคิวในคิวเงื่อนไขก่อน ดังนั้นอาจมีคิวเงื่อนไขหลายเงื่อนไขและคิวเงื่อนไขที่แตกต่างกันถูกตั้งค่าตามเงื่อนไขการรอคอยที่แตกต่างกัน คิวเงื่อนไขเป็นรายการที่เชื่อมโยงทางเดียว อินเตอร์เฟสเงื่อนไขกำหนดการดำเนินการทั้งหมดในคิวเงื่อนไข คลาส ConditionObject ภายใน AbstractqueuedSynchronizer ใช้อินเทอร์เฟซเงื่อนไข ลองมาดูกันว่าการดำเนินการใดที่กำหนดโดยอินเทอร์เฟซเงื่อนไข
เงื่อนไขส่วนต่อประสานสาธารณะ {// รอการตอบสนองต่อการหยุดชะงักของเธรดเป็นโมฆะรอ () พ่น InterruptedException; // การรอไม่ตอบสนองต่อการหยุดชะงักของเธรดเป็นโมฆะรอคอยอย่างต่อเนื่อง (); // เงื่อนไขการตั้งค่าที่รอเวลาสัมพัทธ์ (ไม่มีการหมุน) Awaitnanos ยาว (Long Nanostimeout) พ่น InterruptedException; // เงื่อนไขการตั้งค่าที่รอเวลาสัมพัทธ์ (หมุน) บูลีนรอ (นาน, หน่วย TimeUnit) พ่น InterruptedException; // เงื่อนไขการตั้งค่ารอบูลีนเวลาที่แน่นอน Awaituntil (กำหนดเวลาวันที่) พ่น InterruptedException; // ปลุกสัญญาณโมฆะโหนดหัว () ในคิวเงื่อนไข; // ปลุกโหนดทั้งหมดของเงื่อนไขคิวโมฆะ signalall (); -แม้ว่าอินเทอร์เฟซเงื่อนไขจะกำหนดวิธีการมากมาย แต่ก็แบ่งออกเป็นสองประเภททั้งหมด วิธีการที่เริ่มต้นด้วยการรอคอยคือวิธีการที่เธรดเข้าสู่คิวเงื่อนไขและรอและวิธีการที่เริ่มต้นด้วยสัญญาณคือวิธีที่ "ตื่น" เธรดในคิวเงื่อนไข ควรสังเกตที่นี่ว่าการเรียกวิธีการสัญญาณอาจหรือไม่อาจปลุกเธรด เมื่อเธรดจะถูกปลุกขึ้นอยู่กับสถานการณ์ตามที่จะกล่าวถึงในภายหลัง แต่การเรียกวิธีการสัญญาณจะย้ายเธรดจากคิวเงื่อนไขไปยังหางของคิวการซิงโครไนซ์ เพื่อความสะดวกในการบรรยายเราจะไม่กังวลเกี่ยวกับเรื่องนี้ในขณะนี้ เราจะเรียกวิธีการส่งสัญญาณว่าการทำงานของเธรดคิวการปลุกแบบมีเงื่อนไข โปรดทราบว่ามีวิธีการรอคอย 5 ประเภทคือการรอการตอบสนองของเธรดการตอบสนองการรอการรอการตอบสนองของเธรดที่ไม่ตอบสนองการรอการรอการรอการรอการรอการรอการหมุนของเวลาสัมพัทธ์ มีวิธีการสัญญาณเพียงสองประเภทเท่านั้นคือการทำงานของการปลุกโหนดหัวคิวเงื่อนไขเท่านั้นและตื่นโหนดทั้งหมดของคิวเงื่อนไข วิธีการประเภทเดียวกันนั้นเหมือนกัน เนื่องจากข้อ จำกัด ด้านพื้นที่จึงเป็นไปไม่ได้และไม่จำเป็นต้องพูดคุยเกี่ยวกับวิธีการเหล่านี้อย่างระมัดระวัง เราเพียงแค่ต้องเข้าใจวิธีการตัวแทนหนึ่งวิธีจากนั้นดูวิธีอื่น ๆ เพื่อทำความเข้าใจ ดังนั้นในบทความนี้ฉันจะพูดถึงวิธีการรอคอยและวิธีการส่งสัญญาณโดยละเอียด วิธีการอื่น ๆ จะไม่ถูกกล่าวถึงในรายละเอียด แต่จะโพสต์ซอร์สโค้ดสำหรับการอ้างอิงของคุณ
1. รอการตอบสนองต่อเงื่อนไขของการขัดจังหวะเธรด
// การรอคอยในการตอบสนองต่อเงื่อนไขของการขัดจังหวะการขัดจังหวะ public void สุดท้ายรอคอย () พ่น InterruptedException {// ถ้าเธรดถูกขัดจังหวะข้อยกเว้นจะถูกโยนลงถ้า (thread.interrupted ()) {โยน InterruptedException ใหม่ (); } // เพิ่มเธรดปัจจุบันไปที่หางของโหนดเงื่อนไขคิวโหนด = addConditionWaiter (); // รีลีสเต็มล็อคก่อนที่จะเข้าสู่เงื่อนไขรอ int savedstate = foreLelease (โหนด); inter interruttermode = 0; // เธรดได้รับการรอคอยอย่างมีเงื่อนไขในขณะที่ลูปในขณะที่ (! isonsyncqueue (โหนด)) {// เธรดที่รอเงื่อนไขถูกระงับที่นี่มีหลายกรณีที่เธรดถูกปลุก: // 1 โหนดไปข้างหน้าของคิวการซิงโครไนซ์ถูกยกเลิก // 2 ตั้งค่าสถานะของโหนดไปข้างหน้าของคิวการซิงโครไนซ์เป็นสัญญาณล้มเหลว // 3 โหนดปัจจุบันถูกปลุกขึ้นหลังจากโหนดไปข้างหน้าปล่อยล็อค // เธรดปัจจุบันตรวจสอบทันทีว่าถูกขัดจังหวะหรือไม่ ถ้าเป็นเช่นนั้นหมายความว่าโหนดจะยกเลิกเงื่อนไขที่รออยู่ ในเวลานี้โหนดจะต้องถูกย้ายออกจากคิวเงื่อนไขถ้า ((interruptmode = checkInterruptwhileWaiting (โหนด))! = 0) {break; }} // หลังจากเธรดตื่นขึ้นมามันจะได้รับการล็อคในโหมดพิเศษถ้า (Acquirequeued (โหนด, บันทึก) && interruptMode! = throw_ie) {interruptMode = reinterrupt; } // การดำเนินการนี้ส่วนใหญ่เพื่อป้องกันเธรดจากการขัดจังหวะก่อนสัญญาณส่งผลให้ไม่มีการตัดการเชื่อมต่อจากคิวเงื่อนไขถ้า (node.nextwaiter! = null) {unlinkcancelledwaiters (); } // การประมวลผลขัดจังหวะที่ตอบสนองต่อโหมดขัดจังหวะถ้า (InterruptMode! = 0) {ReportInctInruptaFterWait (InterruptMode); -เมื่อเธรดเรียกวิธีการรอคอยเธรดปัจจุบันจะถูกห่อเป็นโหนดโหนดและวางไว้ในหางของคิวเงื่อนไข ในเมธอด addConditionWaiter หากพบว่ามีการยกเลิกโหนดของคิวเงื่อนไขวิธีการ unlinkcancelledwaiters จะถูกเรียกให้ล้างโหนดที่ถูกยกเลิกทั้งหมดของคิวเงื่อนไข ขั้นตอนนี้คือการเตรียมการสำหรับการแทรกโหนด หลังจากตรวจสอบให้แน่ใจว่าสถานะของโหนดหางเป็นเงื่อนไขยังมีการสร้างโหนดใหม่เพื่อห่อเธรดปัจจุบันและวางไว้ในหางของคิวเงื่อนไข โปรดทราบว่ากระบวนการนี้เพิ่มโหนดลงในหางของคิวการซิงโครไนซ์โดยไม่ระงับเธรด
ขั้นตอนที่ 2: ปล่อยล็อคอย่างสมบูรณ์
// Full Release การล็อค FINAL FOULLRELEESE (โหนดโหนด) {บูลีนล้มเหลว = true; ลอง {// รับสถานะการซิงโครไนซ์ปัจจุบัน int savedstate = getState (); // ใช้สถานะการซิงโครไนซ์ปัจจุบันเพื่อปล่อยล็อคถ้า (release (savedState)) {failed = false; // หากล็อคได้รับการปล่อยตัวสำเร็จให้กลับมาบันทึก; } else {// หากการล็อคถูกปล่อยออกมาล้มเหลวให้โยนข้อยกเว้นรันไทม์โยนใหม่ ungelmonmonitorstateException (); }} ในที่สุด {// ตรวจสอบให้แน่ใจว่าโหนดถูกตั้งค่าเป็นสถานะยกเลิกหาก (ล้มเหลว) {node.waitstatus = node.cancelled; -หลังจากห่อเธรดปัจจุบันลงในโหนดและเพิ่มลงในหางของคิวเงื่อนไขวิธีการอย่างเต็มรูปแบบจะถูกเรียกให้ปล่อยล็อค โปรดทราบว่าวิธีการที่ชื่อ FullRelease ใช้เพื่อปล่อยล็อคอย่างสมบูรณ์เนื่องจากการล็อคนั้นกลับเข้ามาใหม่ดังนั้นคุณต้องปล่อยล็อคก่อนที่จะรอเงื่อนไขมิฉะนั้นอื่น ๆ จะไม่สามารถรับล็อคได้ หากการล็อคถูกปล่อยออกมาล้มเหลวจะมีการโยนข้อยกเว้นรันไทม์ หากล็อคถูกปล่อยออกมาสำเร็จจะกลับไปที่สถานะการซิงโครไนซ์ก่อนหน้า
ขั้นตอนที่ 3: ทำให้เงื่อนไขรอ
// เธรดกำลังรออยู่ในขณะที่ลูปในขณะที่ (! isonsyncqueue (โหนด)) {// เธรดที่กำลังรอเงื่อนไขถูกระงับไว้ที่นี่ มีหลายกรณีที่มีการปลุกเธรด: // 1 โหนดไปข้างหน้าของคิวการซิงโครไนซ์ถูกยกเลิก // 2 ตั้งค่าสถานะของโหนดไปข้างหน้าของคิวการซิงโครไนซ์เป็นสัญญาณล้มเหลว // 3 โหนดปัจจุบันถูกปลุกขึ้นหลังจากโหนดไปข้างหน้าปล่อยล็อค lockksupport.park (นี่); // เธรดปัจจุบันตื่นขึ้นมาทันทีเพื่อตรวจสอบว่าถูกขัดจังหวะหรือไม่ ถ้าเป็นเช่นนั้นหมายความว่าโหนดจะยกเลิกเงื่อนไขที่รออยู่ ในเวลานี้โหนดจะต้องถูกย้ายออกจากคิวเงื่อนไขถ้า ((interruptmode = checkInterruptwhileWaiting (โหนด))! = 0) {break; }} // ตรวจสอบสถานการณ์การหยุดชะงักของเธรดเมื่อเงื่อนไขการรอการตรวจสอบ int private int whileWaiting (โหนดโหนด) {// คำขอขัดจังหวะคือก่อนการดำเนินการสัญญาณ: throw_ie // คำขอขัดจังหวะคือหลังจากการดำเนินการสัญญาณ: reinterrupt // ไม่ได้รับคำขอขัดจังหวะในช่วงเวลานี้ (transferaftercancelledwait (โหนด)? throw_ie: reinterrupt): 0;} // โอนโหนดที่ยกเลิกเงื่อนไขที่รอจากคิวเงื่อนไขไปยังคิวการซิงโครไนซ์บูลีนสุดท้าย transferaftercancelledwait (Node Node) node.condition, 0)) {// หลังจากการปรับเปลี่ยนสถานะสำเร็จให้วางโหนดลงในหางของคิวการซิงโครไนซ์ enq (โหนด); กลับมาจริง; } // สิ่งนี้บ่งชี้ว่าการดำเนินการ CAS ล้มเหลวซึ่งบ่งชี้ว่าการขัดจังหวะเกิดขึ้นหลังจากวิธีการสัญญาณในขณะที่ (! isonsyncqueue (โหนด)) {// ถ้าวิธีการป่วยไม่ได้ถ่ายโอนโหนดไปยังคิวการซิงโครไนซ์ให้รอ thread.yield (); } return false;}หลังจากการดำเนินการสองครั้งข้างต้นเสร็จสมบูรณ์แล้วมันจะเข้าสู่ขณะที่วนซ้ำ คุณจะเห็นว่าในขณะที่ลูปแรกเรียก locksupport.park (นี้) เพื่อแขวนเธรดดังนั้นเธรดจะถูกบล็อกที่นี่ตลอดเวลา หลังจากเรียกใช้วิธีการสัญญาณเพียงถ่ายโอนโหนดจากคิวแบบมีเงื่อนไขไปยังคิวการซิงโครไนซ์ ไม่ว่าเธรดจะถูกปลุกขึ้นอยู่กับสถานการณ์หรือไม่ หากคุณพบว่าโหนดไปข้างหน้าในคิวการซิงโครไนซ์จะถูกยกเลิกเมื่อถ่ายโอนโหนดหรือสถานะของโหนดไปข้างหน้าจะได้รับการอัปเดตเป็นสัญญาณล้มเหลวทั้งสองกรณีจะปลุกเธรดทันที มิฉะนั้นเธรดที่อยู่ในคิวการซิงโครไนซ์จะไม่ถูกปลุกเมื่อสิ้นสุดวิธีการสัญญาณ แต่จะรอจนกว่าโหนดไปข้างหน้าจะตื่นขึ้นมา แน่นอนนอกเหนือจากการเรียกใช้วิธีการสัญญาณให้ตื่นแล้วเธรดยังสามารถตอบสนองต่อการขัดจังหวะ หากเธรดได้รับคำขอขัดจังหวะที่นี่จะดำเนินการต่อไป คุณจะเห็นว่าหลังจากที่เธรดตื่นขึ้นมาแล้วมันจะตรวจสอบทันทีว่ามันถูกปลุกด้วยการขัดจังหวะหรือผ่านวิธีการสัญญาณ หากถูกขัดจังหวะโดยการขัดจังหวะมันจะถ่ายโอนโหนดนี้ไปยังคิวการซิงโครไนซ์ แต่สามารถทำได้โดยการเรียกใช้วิธีการถ่ายโอน หลังจากการดำเนินการขั้นสุดท้ายของขั้นตอนนี้การขัดจังหวะจะถูกส่งคืนและในขณะที่ลูปจะถูกกระโดดออกไป
ขั้นตอนที่ 4: การดำเนินการหลังจากโหนดถูกลบออกจากคิวเงื่อนไข
// หลังจากเธรดตื่นขึ้นมามันจะได้รับการล็อคในโหมดพิเศษถ้า (AcquireQued (โหนด, บันทึก) && interruptmode! = throw_ie) {interruptmode = reinterrupt;} // การดำเนินการนี้ส่วนใหญ่จะป้องกันไม่ให้เธรดจากการขัดจังหวะก่อนสัญญาณ unlinkcancelledwaiters ();} // การประมวลผลขัดจังหวะที่ตอบสนองต่อโหมดขัดจังหวะถ้า (interruptmode! = 0) {reportinterinruptafterwait (interruptmode);} // หลังจากสิ้นสุดสภาพการรอการประมวลผลที่สอดคล้องกัน โยนถ้า (interruptmode == throw_ie) {โยน interruptedException ใหม่ (); // ถ้าโหมดขัดจังหวะกลับมาอีกครั้งมันจะแขวนตัวเอง} อื่นถ้า (interruptmode == reinterrupt) {selfInterrupt (); -เมื่อเธรดสิ้นสุดลงในขณะที่ลูปนั่นคือเงื่อนไขจะรอมันจะกลับไปที่คิวการซิงโครไนซ์ ไม่ว่าจะเป็นเพราะการเรียกวิธีการสัญญาณกลับหรือเนื่องจากการหยุดชะงักของเธรดโหนดจะอยู่ในคิวซิงโครนัสในที่สุด ในเวลานี้วิธี Acquirequeued จะถูกเรียกให้ดำเนินการในการรับล็อคในคิวการซิงโครไนซ์ เราได้พูดถึงวิธีการนี้โดยละเอียดในบทความโหมดพิเศษแล้ว กล่าวอีกนัยหนึ่งหลังจากโหนดออกมาจากคิวเงื่อนไขมันจะไปที่ชุดล็อคในโหมดพิเศษ หลังจากโหนดนี้ได้รับการล็อคอีกครั้งมันจะเรียกวิธีการรายงาน interruptafterwait เพื่อตอบสนองตามสถานการณ์การขัดจังหวะในช่วงเวลานี้ หากการขัดจังหวะเกิดขึ้นก่อนวิธีการสัญญาณ InterruptMode คือ throw_ie และจะมีการโยนข้อยกเว้นหลังจากล็อคได้รับอีกครั้ง หากการขัดจังหวะเกิดขึ้นหลังจากวิธีการสัญญาณ InterruptMode จะถูกขัดจังหวะอีกครั้งและจะถูกขัดจังหวะอีกครั้งหลังจากได้รับการล็อคอีกครั้ง
2. กำลังรอการตอบสนองต่อการขัดจังหวะเธรด
// การรอเป็นโมฆะสาธารณะสุดท้ายรอคอยอย่างต่อเนื่อง () {// เพิ่มเธรดปัจจุบันลงในหางของโหนดโหนดคิวเงื่อนไข = addConditionWaiter (); // การปล่อยเต็มการล็อคและส่งคืนสถานะการซิงโครไนซ์ปัจจุบัน int savedstate = foreLeRelease (โหนด); บูลีนขัดจังหวะ = false; // โหนดกำลังรออย่างมีเงื่อนไขในขณะที่ลูปในขณะที่ (! isonsyncqueue (โหนด)) {// เธรดทั้งหมดในคิวเงื่อนไขจะถูกระงับไว้ที่นี่ lockksupport.park (นี่); // เธรดตื่นขึ้นมาและพบว่าการขัดจังหวะจะไม่ตอบสนองทันทีถ้า (thread.interrupted ()) {interrupted = true; }} ถ้า (ได้รับ (โหนด, บันทึก) || ถูกขัดจังหวะ) {// ตอบสนองต่อคำขอขัดจังหวะทั้งหมดที่นี่หากเป็นไปตามเงื่อนไขสองข้อต่อไปนี้มันจะแขวนตัวเอง // 1 เธรดได้รับคำขอขัดจังหวะในขณะที่เงื่อนไขกำลังรอ // 2 เธรดได้รับคำขอขัดจังหวะในวิธีการที่ได้รับ selfinterrupt (); -3. ตั้งค่าเงื่อนไขเวลาสัมพัทธ์ (ไม่มีการหมุน)
// ตั้งค่าเงื่อนไขการรอกำหนดเวลา (เวลาสัมพัทธ์) และอย่าดำเนินการสปินรอการรอสาธารณะรอบสุดท้ายที่ยาวนาน Awaitnanos (ยาว nanostimeout) โยน interruptedException {// ถ้าเธรดถูกขัดจังหวะข้อยกเว้นจะถูกโยนลงถ้า (เธรด } // เพิ่มเธรดปัจจุบันไปที่หางของโหนดเงื่อนไขคิวโหนด = addConditionWaiter (); // การปล่อยล็อคเต็มก่อนที่จะเข้าสู่เงื่อนไขการรอ int savedstate = foreLelease (โหนด); สุดท้ายเวลา = system.nanotime (); inter interruttermode = 0; ในขณะที่ (! isonsyncqueue (โหนด)) {// ตัดสินว่าการหมดเวลาจะใช้งานได้หรือไม่ถ้า (nanostimeout <= 0l) {// ถ้าหมดเวลาเสร็จสิ้นคุณต้องดำเนินการตามเงื่อนไขการยกเลิกการดำเนินการ หยุดพัก; } // แขวนเธรดปัจจุบันเป็นระยะเวลาหนึ่งเธรดอาจถูกปลุกให้ตื่นในช่วงเวลานี้หรืออาจตื่นขึ้นมาด้วยตัวเอง locksupport.parknanos (นี่, nanostimeout); // ตรวจสอบข้อมูลอินเตอร์รัปต์ก่อนหลังจากเธรดจะตื่นขึ้นมาถ้า ((InterffMode = CheckInterruptWhiLEWaiting (Node))! = 0) {break; } long now = system.nanotime (); // เวลาหมดเวลาลบเวลารอคอยของเงื่อนไข nanostimeout - = ตอนนี้ - เมื่อเวลาผ่านไป; เมื่อเวลาผ่านไป = ตอนนี้; } // หลังจากเธรดตื่นขึ้นมามันจะได้รับการล็อคในโหมดพิเศษถ้า (Acquirequeued (Node, SavedState) && InterruptMode! = throw_ie) {interruptMode = reinterrupt; } // เนื่องจากวิธีการ transferaftercancelledwait ไม่ว่างเปล่า nextwaiter สิ่งที่คุณต้องทำความสะอาดที่นี่ถ้า (node.nextwaiter! = null) {unlinkcancelledwaiters (); } // การประมวลผลขัดจังหวะที่ตอบสนองต่อโหมดขัดจังหวะถ้า (InterruptMode! = 0) {ReportInctInruptaFterWait (InterruptMode); } // ส่งคืนเวลาที่เหลือคืน nanostimeout - (system.nanotime () - ล่าสุด);}4. ตั้งค่าเงื่อนไขเวลาสัมพัทธ์ (หมุน)
// ตั้งค่าเงื่อนไขการรอกำหนดเวลา (เวลาสัมพัทธ์) ดำเนินการสปินรอบูลีนสุดท้ายรอคอย (นาน, หน่วยเวลา) พ่น InterruptedException {ถ้า (หน่วย == null) {โยน nullpointerexception ใหม่ (); } // รับมิลลิวินาทีของการหมดเวลา Long Nanostimeout = unit.tonanos (เวลา); // หากเธรดถูกขัดจังหวะข้อยกเว้นจะถูกโยนลงไปถ้า (เธรด interrupted ()) {โยน interruptedException ใหม่ (); } // เพิ่มเธรดปัจจุบันไปที่หางของโหนดเงื่อนไขคิวโหนด = addConditionWaiter (); // การปล่อยล็อคเต็มก่อนที่จะเข้าสู่เงื่อนไขเพื่อรอ int savedstate = foreLelease (โหนด); // รับมิลลิวินาทีของเวลาล่าสุดในเวลาสุดท้าย = System.nanotime (); บูลีน timedout = false; inter interruttermode = 0; ในขณะที่ (! isonsyncqueue (โหนด)) {// ถ้าหมดเวลาหมดเวลาคุณจะต้องดำเนินการยกเลิกเงื่อนไขการดำเนินการหาก (nanostimeout <= 0l) {timedout = transferaftercancelledwait (Node); หยุดพัก; } // หากเวลาหมดเวลามากกว่าเวลาหมุนเธรดจะถูกระงับเป็นระยะเวลาหนึ่งถ้า (nanostimeout> = spinfortimeoutthreshold) {locksupport.parknanos (นี่, nanostimeout); } // หลังจากเธรดตื่นแล้วให้ตรวจสอบข้อมูลอินเตอร์รัปต์ก่อนถ้า ((interffMode = CheckInterruptWhiLewaiting (โหนด))! = 0) {break; } long now = system.nanotime (); // เวลาหมดเวลาในแต่ละครั้งจะลบเวลาของเงื่อนไขที่รอ nanostimeout - = ตอนนี้ - ล่าสุด; เมื่อเวลาผ่านไป = ตอนนี้; } // หลังจากเธรดตื่นขึ้นมามันจะได้รับการล็อคในโหมดพิเศษถ้า (Acquirequeued (Node, SavedState) && InterruptMode! = throw_ie) {interruptMode = reinterrupt; } // เนื่องจากวิธีการ transferaftercancelledwait ไม่ว่างเปล่า nextwaiter สิ่งที่คุณต้องทำความสะอาดที่นี่ถ้า (node.nextwaiter! = null) {unlinkcancelledwaiters (); } // การประมวลผลขัดจังหวะที่ตอบสนองต่อโหมดขัดจังหวะถ้า (InterruptMode! = 0) {ReportInctInruptaFterWait (InterruptMode); } // ส่งคืนว่าธงหมดเวลาส่งคืน! timedout;}5. ตั้งค่าเงื่อนไขการรอเวลาสัมบูรณ์
// ตั้งค่าเงื่อนไขการรอกำหนดเวลา (เวลาสัมบูรณ์) บูลีนสุดท้าย Boolean Awaituntil (กำหนดเวลาวันที่) พ่น InterruptedException {ถ้า (กำหนดเวลา == null) {โยน nullpointerexception ใหม่ (); } // รับมิลลิวินาทีของเวลา abstime ที่ยาวนานอย่างแน่นอน = กำหนดเวลา getTime (); // หากเธรดถูกขัดจังหวะข้อยกเว้นจะถูกโยนลงไปถ้า (เธรด interrupted ()) {โยน interruptedException ใหม่ (); } // เพิ่มเธรดปัจจุบันไปที่หางของโหนดเงื่อนไขคิวโหนด = addConditionWaiter (); // รีลีสเต็มล็อคก่อนที่จะเข้าสู่เงื่อนไขรอ int savedstate = foreLelease (โหนด); บูลีน timedout = false; inter interruttermode = 0; ในขณะที่ (! isonsyncqueue (โหนด)) {// ถ้าหมดเวลาคุณจะต้องดำเนินการยกเลิกเงื่อนไขการรอการดำเนินการหาก (System.currentTimeMillis ()> abstime) {timeDout = transferaftercancelledwait (Node); หยุดพัก; } // แขวนเธรดเป็นระยะเวลาหนึ่งในระหว่างที่เธรดอาจตื่นขึ้นมาหรืออาจถึงเวลาที่จะต้องตื่นขึ้นมาด้วยตัวเอง locksupport.parkuntil (นี่, abstime); // ตรวจสอบข้อมูลอินเตอร์รัปต์ก่อนหลังจากเธรดตื่นขึ้นมา ((InterruptMode = CheckInterruptWhileWaiting (Node))! = 0) {break; }} // หลังจากเธรดตื่นขึ้นมามันจะได้รับการล็อคในโหมดพิเศษถ้า (Acquirequeued (โหนด, บันทึก) && interruptMode! = throw_ie) {interruptMode = reinterrupt; } // เนื่องจากวิธีการ transferaftercancelledwait ไม่ว่างเปล่า nextwaiter สิ่งที่คุณต้องทำความสะอาดที่นี่ถ้า (node.nextwaiter! = null) {unlinkcancelledwaiters (); } // การประมวลผลขัดจังหวะที่ตอบสนองต่อโหมดขัดจังหวะถ้า (InterruptMode! = 0) {ReportInctInruptaFterWait (InterruptMode); } // ส่งคืนว่าธงหมดเวลาส่งคืน! timedout;}6. ปลุกโหนดหัวในคิวแบบมีเงื่อนไข
// ปลุกโหนดถัดไปในเงื่อนไขคิวสาธารณะสัญญาณโมฆะสุดท้าย () {// ตัดสินว่าเธรดปัจจุบันถือล็อคถ้า (! isheldexclusively ()) {โยนใหม่ unlegalMonitorStateException (); } โหนดแรก = FirstWaiter; // ถ้ามีคิวในคิวเงื่อนไขถ้า (แรก! = null) {// ปลุกโหนดหัวในเงื่อนไขคิว dosignal (แรก); }} // ปลุกโหนดหัวในเงื่อนไขคิวส่วนตัวโมฆะ dosignal (โหนดแรก) {ทำ {// 1 ย้ายการอ้างอิง firstwaiter ทีละคนถ้า ((firstwaiter = first.nextwaiter) == null) {lastwaiter = null; } // 2 ล้างการอ้างอิงของโหนดผู้สืบทอดของโหนดหัวก่อน NEXTWAITER = NULL; // 3. โอนโหนดหัวไปยังคิวการซิงโครไนซ์และเป็นไปได้ที่จะปลุกเธรดหลังจากการถ่ายโอนเสร็จสิ้น // 4 หากการดำเนินการ transferforsignal ล้มเหลวให้ตื่นโหนดถัดไป} ในขณะที่ (! transferforsignal (ครั้งแรก) && (first = firstwaiter)! = null);} // โอนโหนดที่ระบุจากคิวเงื่อนไขไปยังคิวการซิงโครไนซ์ node.condition, 0)) {// หากการดำเนินการเพื่ออัปเดตสถานะล้มเหลวให้ส่งคืนเท็จโดยตรง // อาจเป็นไปได้ว่าวิธีการ transferaftercancelledwait เปลี่ยนสถานะก่อนทำให้การดำเนินการ CAS นี้ล้มเหลวส่งคืนเท็จ; } // เพิ่มโหนดนี้ลงในหางของโหนดการซิงโครไนซ์คิว p = enq (โหนด); int ws = p.waitstatus; if (ws> 0 ||! ComperendsetWaitStatus (P, WS, Node.Signal)) {// เธรดปัจจุบันจะถูกปลุกเมื่อสถานการณ์ต่อไปนี้เกิดขึ้น // 1 โหนดไปข้างหน้าอยู่ในสถานะยกเลิก // 2 สถานะของการอัปเดตโหนดไปข้างหน้าคือการทำงานของสัญญาณล้มเหลว locksupport.unpark (node.thread); } return true;}จะเห็นได้ว่าแกนกลางที่สุดของวิธีการสัญญาณคือการเรียกใช้วิธีการ TransferSignal ในวิธีการ transferforsignal ให้ใช้การดำเนินการ CAS ก่อนเพื่อตั้งค่าสถานะของโหนดจากเงื่อนไขเป็น 0 จากนั้นเรียกใช้เมธอด Enq เพื่อเพิ่มโหนดไปที่หางของคิวการซิงโครไนซ์ เราจะเห็นคำพิพากษาต่อไปหากคำพิพากษา คำสั่งการตัดสินนี้ส่วนใหญ่จะใช้เพื่อพิจารณาว่าจะปลุกเธรดเมื่อใด หากสถานการณ์ทั้งสองนี้เกิดขึ้นเธรดจะถูกปลุกขึ้นทันที หนึ่งคือเมื่อมีการค้นพบว่าสถานะของโหนดก่อนหน้าถูกยกเลิกและอื่น ๆ คือเมื่อสถานะของโหนดก่อนหน้านี้ไม่สามารถอัปเดตได้ ทั้งสองกรณีจะปลุกเธรดทันทีมิฉะนั้นจะทำโดยเพียงแค่ถ่ายโอนโหนดจากคิวแบบมีเงื่อนไขไปยังคิวการซิงโครไนซ์และจะไม่ปลุกเธรดในโหนดทันที วิธีการ SignalAll นั้นคล้ายกันอย่างคร่าวๆยกเว้นว่าจะวนซ้ำผ่านโหนดทั้งหมดในคิวแบบมีเงื่อนไขและถ่ายโอนไปยังคิวแบบซิงโครนัส วิธีการถ่ายโอนโหนดยังคงเรียกวิธีการ transferforsignal
7. ปลุกโหนดทั้งหมดของคิวเงื่อนไข
// ปลุกโหนดทั้งหมดที่อยู่ด้านหลังเงื่อนไขคิวสาธารณะสุดท้ายเป็นโมฆะสัญญาณ void () {// ตัดสินว่าเธรดปัจจุบันถือล็อคถ้า (! isheldexclusively ()) {โยน unlegalmonitonstateException ใหม่ (); } // รับโหนดส่วนหัวของคิวเงื่อนไขโหนดก่อน = FirstWaiter; ถ้า (แรก! = null) {// ปลุกโหนดทั้งหมดของเงื่อนไขคิว dosignalall (ครั้งแรก); }} // ปลุกโหนดทั้งหมดของเงื่อนไขคิวส่วนตัวโมฆะ dosignalall (โหนดแรก) {// ก่อนที่จะว่างการอ้างอิงของโหนดส่วนหัวและโหนดหาง lastwaiter = firstwaiter = null; ทำ {// รับการอ้างอิงของโหนดผู้สืบทอดก่อน = first.nextwaiter; // ล้างการอ้างอิงที่ตามมาของโหนดที่จะถ่ายโอนก่อน. nextwaiter = null; // โอนโหนดจากคิวเงื่อนไขไปยังคิวการซิงโครไนซ์ TransferSignal (ครั้งแรก); // ชี้การอ้างอิงไปยังโหนดถัดไปก่อน = ถัดไป; } ในขณะที่ (แรก! = null);}ณ จุดนี้การวิเคราะห์ซอร์สโค้ดที่เป็นนามธรรมทั้งหมดของเราทั้งหมดของเราสิ้นสุดลงแล้ว ฉันเชื่อว่าด้วยการวิเคราะห์ทั้งสี่นี้ทุกคนสามารถควบคุมและเข้าใจ AQS ได้ดีขึ้น หมวดหมู่นี้มีความสำคัญมากเพราะมันเป็นรากฐานที่สำคัญของหมวดหมู่การซิงโครไนซ์อื่น ๆ อีกมากมาย เนื่องจากระดับ จำกัด และความสามารถในการแสดงออกของผู้เขียนหากไม่มีข้อความที่ชัดเจนหรือความเข้าใจที่ไม่เพียงพอโปรดแก้ไขให้ถูกต้องในเวลาและพูดคุยและเรียนรู้ร่วมกัน คุณสามารถฝากข้อความเพื่ออ่านปัญหาด้านล่าง หากคุณต้องการซอร์สโค้ดความคิดเห็น AQS คุณสามารถติดต่อผู้เขียนเพื่อขอ
หมายเหตุ: การวิเคราะห์ทั้งหมดข้างต้นขึ้นอยู่กับ JDK1.7 และจะมีความแตกต่างระหว่างเวอร์ชันที่แตกต่างกันผู้อ่านจำเป็นต้องให้ความสนใจ
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น