ผ่านการวิเคราะห์ในบทความก่อนหน้านี้เรารู้ว่ามีสามวิธีในการรับล็อคด้วยโหมดพิเศษคือเพื่อให้ได้โดยไม่ต้องตอบสนองต่อการตอบสนองของเธรดเพื่อให้ได้การขัดจังหวะเธรดการตอบสนองและเพื่อให้ได้เวลาหมดเวลา นอกจากนี้ยังมีสามวิธีในการรับล็อคในโหมดที่ใช้ร่วมกันและโดยทั่วไปจะเหมือนกัน หากเราหาทางเดียวเราสามารถเข้าใจวิธีอื่นได้อย่างรวดเร็ว แม้ว่าซอร์สโค้ด AbstractqueuedSynchronizer มีมากกว่าหนึ่งพันบรรทัด แต่ก็มีการทำซ้ำหลายครั้งดังนั้นผู้อ่านไม่ควรกลัวในตอนแรก เพียงแค่อ่านอย่างอดทนและช้าคุณจะค่อยๆเข้าใจมันโดยธรรมชาติ จากประสบการณ์ส่วนตัวของฉันมีแง่มุมที่สำคัญอีกหลายประการที่จะเข้าใจเมื่ออ่านซอร์สโค้ด Abstractqueuedynchronizer กล่าวคือความแตกต่างระหว่างโหมดพิเศษและโหมดที่ใช้ร่วมกันสถานะการรอของโหนดและความเข้าใจของคิวแบบมีเงื่อนไข หากคุณเข้าใจประเด็นสำคัญเหล่านี้การอ่านซอร์สโค้ดที่ตามมาจะง่ายกว่ามาก แน่นอนสิ่งเหล่านี้ได้รับการแนะนำในบทความของฉัน "Java Concurrency Series [1] ---- การวิเคราะห์ซอร์สโค้ด Abstractqueuedynchronizer" และผู้อ่านสามารถตรวจสอบได้ก่อน บทความนี้วิเคราะห์โหมดการแชร์เป็นสามวิธีในการรับล็อคและวิธีหนึ่งในการปล่อยล็อค
1. ไม่ตอบสนองต่อการเข้าซื้อกิจการแบบอินเตอร์รัปต์เธรด
// การได้รับล็อคในโหมดที่ไม่สามารถแก้ไขได้ (โหมดที่ใช้ร่วมกัน) โมฆะสุดท้ายของสาธารณะได้รับ (int arg) {// 1 ลองรับล็อคถ้า (tryacquireshared (arg) <0) {// 2 หากการได้มาล้มเหลวให้ป้อนวิธีนี้ doacquireshared (arg); }} // ลองรับการล็อค (โหมดที่ใช้ร่วมกัน) // จำนวนลบ: ระบุว่าการได้มาล้มเหลว // ค่าศูนย์: ระบุว่าโหนดปัจจุบันได้รับสำเร็จ แต่โหนดผู้สืบทอดไม่สามารถรับได้อีกต่อไป UnsupportedoperationException ();}การเรียกวิธีการซื้อกิจการเป็นวิธีการรับล็อคโดยไม่ตอบสนองต่อการขัดจังหวะเธรด ในวิธีนี้ Tryacquireshare จะถูกเรียกตัวครั้งแรกเพื่อพยายามรับการล็อค วิธี Tryacquireshared ส่งคืนสถานะของการรับล็อค ที่นี่ AQS ระบุว่าหากสถานะการส่งคืนเป็นลบหมายความว่าโหนดปัจจุบันไม่สามารถรับล็อคได้ หาก 0 หมายความว่าโหนดปัจจุบันจะได้รับการล็อค แต่ไม่สามารถรับโหนดที่ตามมาได้อีกต่อไป หากเป็นบวกก็หมายความว่าโหนดปัจจุบันจะได้รับการล็อคและโหนดที่ตามมาของการล็อคนี้สามารถได้รับได้สำเร็จ เมื่อคลาสย่อยใช้ตรรกะของการได้รับการล็อคโดยวิธี tryacquireshared ค่าคืนจะต้องปฏิบัติตามอนุสัญญานี้ หากค่าส่งคืนของการโทร tryacquireshared น้อยกว่า 0 นั่นหมายความว่าความพยายามที่จะได้รับการล็อคล้มเหลว ถัดไปเรียกใช้วิธี doacquireshared เพื่อเพิ่มเธรดปัจจุบันไปยังคิวการซิงโครไนซ์ เราเห็นวิธี doacquireshared
// รับ (โหมดที่ใช้ร่วมกัน) ในคิวการซิงโครไนซ์เป็นโมฆะส่วนตัว doacquireshared (int arg) {// เพิ่มลงในคิวการซิงโครไนซ์โหนดสุดท้ายโหนด = addwaiter (node.shared); บูลีนล้มเหลว = จริง; ลอง {บูลีนขัดจังหวะ = false; สำหรับ (;;) {// รับโหนดไปข้างหน้าของโหนดสุดท้ายโหนดสุดท้ายโหนด p = node.predepector (); // ถ้าโหนดไปข้างหน้าเป็นโหนดหัวให้ลองรับการล็อคอีกครั้งถ้า (p == หัว) {// พยายามที่จะรับล็อคอีกครั้งและส่งคืนสถานะการได้มา // r <0 แสดงว่าการได้มานั้นล้มเหลว // r = 0 แสดงว่าโหนดปัจจุบันได้รับความสำเร็จ ได้รับ int r = tryacquireshared (ARG) สำเร็จ; ถ้า (r> = 0) {// ถึงจุดสิ้นสุดนี้จะระบุว่าโหนดปัจจุบันได้รับการล็อคสำเร็จ ในเวลานี้มันจะเผยแพร่ข้อมูลสถานะล็อคไปยังโหนดที่ตามมา setheadandpropagate (Node, R); p.next = null; // หากได้รับคำขอขัดจังหวะระหว่างการบล็อกเธรดให้ตอบกลับคำขอในขั้นตอนนี้หาก (ขัดจังหวะ) {selfInterrupt (); } ล้มเหลว = false; กลับ; }} // ทุกครั้งที่การล็อคล้มเหลวมันจะพิจารณาว่าสามารถระงับเธรดได้หรือไม่ หากเป็นไปได้เธรดจะถูกระงับในวิธี parkandcheckinterrupt ถ้า (ควร parkafterferfailedacquire (p, โหนด) && parkandcheckinterrupt ()) {interruthed = true; }}} ในที่สุด {ถ้า (ล้มเหลว) {cancelacquire (โหนด); -การป้อนวิธี doacquireshared ก่อนให้เรียกใช้วิธี addwaiter เพื่อห่อเธรดปัจจุบันลงในโหนดและวางไว้ที่ส่วนท้ายของคิวการซิงโครไนซ์ เราได้พูดคุยเกี่ยวกับกระบวนการเพิ่มโหนดเมื่อพูดถึงโหมดพิเศษดังนั้นฉันจะไม่พูดถึงที่นี่ หลังจากโหนดเข้าสู่คิวการซิงโครไนซ์หากพบว่าโหนดด้านหน้าของมันคือโหนดหัวเนื่องจากเธรดของโหนดหัวได้รับล็อคและเข้าห้อง ดังนั้นโหนดปัจจุบันจะไม่แขวนตัวเองก่อน แต่จะพยายามรับการล็อคอีกครั้ง หากบุคคลที่อยู่ด้านหน้าเพียงแค่ปล่อยล็อคและใบไม้โหนดปัจจุบันสามารถรับล็อคได้สำเร็จ หากบุคคลที่อยู่ข้างหน้าไม่ได้ปล่อยล็อคมันจะเรียกวิธีการ ParkafterfterFailedAcquire ในวิธีนี้สถานะของโหนดหัวจะเปลี่ยนเป็นสัญญาณ โดยตรวจสอบให้แน่ใจว่าสถานะของโหนดก่อนหน้าคือสัญญาณโหนดปัจจุบันสามารถแขวนอยู่ด้วยความมั่นใจ เธรดทั้งหมดจะถูกระงับในวิธี ParkandCheckInterrupt หากโหนดปัจจุบันเกิดขึ้นเพื่อรับการล็อคได้สำเร็จแล้ววิธี SetheadandPropagate จะถูกเรียกให้ตั้งค่าตัวเองเป็นโหนดหัวและปลุกโหนดที่อยู่ด้านหลังด้วย ลองมาดูการดำเนินการเฉพาะของวิธี SetheadandPropagate
// ตั้งค่าโหนดหัวและเผยแพร่สถานะของล็อค (โหมดที่ใช้ร่วมกัน) โมฆะส่วนตัว setheadandpropagate (โหนดโหนด, int แพร่กระจาย) {node h = head; // ตั้งค่าโหนดที่กำหนดเป็นโหนดหัว sethead (โหนด); // ถ้าการแพร่กระจายมากกว่า 0 หมายความว่าการล็อคสามารถรับได้ถ้า (แพร่กระจาย> 0 || h == null || h.waitstatus <0) {// รับโหนดผู้สืบทอดของโหนดโหนดที่กำหนด s = node.next; // ถ้าโหนดผู้สืบทอดของโหนดที่กำหนดว่างเปล่าหรือสถานะของมันเป็นสถานะที่ใช้ร่วมกันถ้า (s == null || s.isshared ()) {// ปลุกโหนดผู้สืบทอด doreleaseshared (); }}} // การดำเนินการล็อค (โหมดที่ใช้ร่วมกัน) โมฆะส่วนตัว doreleaseshared () {สำหรับ (;;) {// รับโหนดหัวของโหนดคิวคิวซิงโครนัส h = head; if (h! = null && h! = tail) {// รับสถานะการรอของโหนดหัว int int ws = h.waitstatus; // ถ้าสถานะของโหนดหัวคือสัญญาณก็หมายความว่ามีคนเข้าคิวอยู่ข้างหลังถ้า (ws == node.signal) {// รับสถานะการรอคอยของโหนดหัวถึง 0 ถ้า (! pompereandsetwaitstatus (h, node.signal, 0)) {ดำเนินการต่อ; } // ปลุกโหนดผู้สืบทอด Unparksuccessor (H); // ถ้าสถานะของโหนดหัวคือ 0 นั่นหมายความว่าไม่มีใครเข้าคิวในภายหลังเพียงแค่แก้ไขสถานะหัวเพื่อเผยแพร่} อื่นถ้า (ws == 0 &&! เปรียบเทียบกับการเปรียบเทียบ (h, 0, node.propagate)) {ดำเนินการต่อ; }} // เท่านั้นโดยตรวจสอบให้แน่ใจว่าโหนดหัวยังไม่ได้รับการแก้ไขในช่วงระยะเวลาคุณสามารถแยกออกจากลูปถ้า (h == หัว) {break; -การเรียกวิธี SetheadandPropagate ก่อนกำหนดตัวเองเป็นโหนดหัวแล้วตัดสินใจว่าจะปลุกโหนดผู้สืบทอดตามค่าการส่งคืนของวิธี tryacquireshared ที่ผ่านมาหรือไม่ ดังที่ได้กล่าวไว้ก่อนหน้านี้เมื่อค่าส่งคืนมากกว่า 0 หมายความว่าโหนดปัจจุบันได้รับการล็อคสำเร็จและโหนดที่ตามมาสามารถรับล็อคได้สำเร็จ ในเวลานี้โหนดปัจจุบันจำเป็นต้องปลุกโหนดที่อยู่ในโหมดที่ใช้ร่วมกัน โปรดทราบว่าทุกครั้งที่คุณตื่นขึ้นมามันจะตื่นขึ้นมาที่โหนดถัดไปเท่านั้น หากโหนดหลังไม่ได้อยู่ในโหมดที่ใช้ร่วมกันโหนดปัจจุบันจะเข้าสู่ห้องโดยตรงและจะไม่ตื่นขึ้นมาที่โหนดเพิ่มเติม การทำงานของการตื่นโหนดผู้สืบทอดในโหมดที่ใช้ร่วมกันดำเนินการในวิธี Doreleaseshared การดำเนินการปลุกของโหมดที่ใช้ร่วมกันและโหมดพิเศษนั้นเหมือนกัน ทั้งสองค้นหาแบรนด์บนที่นั่งของคุณ (รอสถานะ) หากแบรนด์เป็นสัญญาณก็หมายความว่ามีคนต้องการช่วยปลุกในภายหลัง หากแบรนด์เป็น 0 หมายความว่าไม่มีใครเข้าคิวในคิวในเวลานี้ ในโหมดพิเศษหากคุณพบว่าไม่มีใครเข้าคิวคุณจะออกจากคิวโดยตรง ในโหมดที่ใช้ร่วมกันหากคุณพบว่าไม่มีใครเข้าคิวอยู่หลังคิวโหนดปัจจุบันจะยังคงทิ้งโน้ตเล็ก ๆ ไว้ก่อนออกเดินทาง (ตั้งค่าสถานะการรอคอยที่จะเผยแพร่) เพื่อบอกผู้คนในภายหลังว่าสถานะที่มีอยู่ของล็อคนี้ จากนั้นเมื่อบุคคลที่มาในภายหลังสามารถตัดสินได้ว่าจะได้รับการล็อคโดยตรงตามสถานะนี้หรือไม่
2. การตอบสนองต่อการได้มาซึ่งเธรดขัดจังหวะ
// การรับล็อคในโหมดขัดจังหวะ (โหมดที่ใช้ร่วมกัน) เป็นโมฆะสุดท้ายที่เป็นโมฆะ acquiresharedinctibly (int arg) พ่น InterruptedException {// ก่อนกำหนดว่าเธรดจะถูกขัดจังหวะถ้าเป็นเช่นนั้นให้โยนข้อยกเว้นถ้า (เธรด } // 1 ลองรับล็อคถ้า (tryacquireshared (arg) <0) {// 2 หากการได้มาล้มเหลวให้ป้อนวิธีนี้ doacquiresharedinctibly (arg); }} // การได้มาในโหมดขัดจังหวะ (โหมดที่ใช้ร่วมกัน) โมฆะส่วนตัว doacquiresharedinctibly (int arg) พ่น InterruptedException {// แทรกโหนดปัจจุบันลงในหางของคิวการซิงโครไนซ์โหนดสุดท้ายโหนด = addwaiter (node.shared); บูลีนล้มเหลว = จริง; ลอง {for (;;) {// รับโหนดสุดท้ายโหนดสุดท้าย p = node.predepector (); if (p == หัว) {int r = tryacquireshared (arg); if (r> = 0) {SetheadandPropagate (Node, R); p.next = null; ล้มเหลว = เท็จ; กลับ; }} if (ควร parkafterferfailedacquire (p, โหนด) && parkandcheckinterrupt ()) {// ถ้าเธรดได้รับคำขอขัดจังหวะในระหว่างกระบวนการบล็อกมันจะโยนข้อยกเว้นทันทีที่นี่ }}} ในที่สุด {ถ้า (ล้มเหลว) {cancelacquire (โหนด); -วิธีการรับล็อคเพื่อตอบสนองต่อการขัดจังหวะเธรดและวิธีการรับล็อคเพื่อตอบสนองต่อการขัดจังหวะเธรดนั้นเหมือนกันในกระบวนการ ความแตกต่างเพียงอย่างเดียวคือสถานที่ที่จะตอบสนองต่อคำขอขัดจังหวะเธรด เมื่อการขัดจังหวะเธรดไม่ตอบสนองต่อการขัดจังหวะเธรดเพื่อรับล็อคเธรดจะถูกปลุกจากวิธี Parkandcheckinterrupt หลังจากการปลุกจะส่งคืนทันทีว่าได้รับคำขอขัดจังหวะหรือไม่ แม้ว่าจะได้รับคำขอขัดจังหวะมันจะยังคงหมุนต่อไปจนกว่าจะได้รับจนกว่าจะตอบสนองต่อคำขอขัดจังหวะและแขวนเอง เธรดจะตอบสนองต่อคำขอขัดจังหวะทันทีหลังจากที่มีการปลุกเธรด หากได้รับการขัดจังหวะเธรดในระหว่างกระบวนการปิดกั้นการขัดจังหวะการรับรู้จะถูกโยนทันที
3. ตั้งเวลาหมดเวลาเพื่อให้ได้
// การรับล็อคด้วยการหมดเวลา จำกัด (โหมดที่ใช้ร่วมกัน) บูลีนสุดท้าย tryacquiresharednanos (int arg, long nanostimeout) โยน interruptedexception {ถ้า (thread.interrupted ()) {โยน interruptedexception ใหม่ (); } // 1 โทรหา tryacquireshare เพื่อพยายามรับล็อค // 2 หากการได้มาล้มเหลวให้โทรหา doacquiresharednanos กลับ tryacquireshared (arg)> = 0 || doacquiresharednanos (arg, nanostimeout);} // การล็อคด้วยการหมดเวลา จำกัด (โหมดที่ใช้ร่วมกัน) บูลีนส่วนตัว doacquiresharednanos (int arg, long nanostimeout) โยน interruptedexception โหนดสุดท้ายโหนด = addWaiter (node.shared); บูลีนล้มเหลว = จริง; ลอง {สำหรับ (;;) {// รับโหนดก่อนหน้าของโหนดสุดท้ายโหนดสุดท้าย p = node.predepector (); if (p == หัว) {int r = tryacquireshared (arg); if (r> = 0) {SetheadandPropagate (Node, R); p.next = null; ล้มเหลว = เท็จ; กลับมาจริง; }} // หากหมดเวลาแล้วการได้มาจะถูกยกเลิกและข้อมูลความล้มเหลวจะถูกส่งคืนหาก (nanostimeout <= 0) {return false; } // 1 ตรวจสอบว่าข้อกำหนดการระงับเธรดนั้นตรงหรือไม่ (รับประกันสถานะโหนดไปข้างหน้าเป็นสัญญาณ) // 2 ตรวจสอบว่าเวลาหมดเวลามากกว่าเวลาหมุนหรือไม่ถ้า (ควร parkafterfterfailedacquire (p, โหนด) && nanostimeout> spinfortimeoutthold) {// ถ้าเป็นไปตามเงื่อนไขทั้งสองข้างต้นเธรดปัจจุบันจะถูกระงับเป็นระยะเวลาหนึ่ง } long now = system.nanotime (); // เวลาหมดเวลาทุกครั้งที่ลบเวลาของการล็อค Nanostimeout - = ตอนนี้ - ล่าสุด; เมื่อเวลาผ่านไป = ตอนนี้; // หากได้รับคำขอขัดจังหวะระหว่างการบล็อกข้อยกเว้นจะถูกโยนทันทีหาก (thread.interrupted ()) {โยน InterruptedException ใหม่ (); }}} ในที่สุด {ถ้า (ล้มเหลว) {cancelacquire (โหนด); -หากคุณเข้าใจวิธีการซื้อสองวิธีข้างต้นมันจะง่ายมากที่จะตั้งค่าวิธีการซื้อของเวลาหมดเวลา กระบวนการพื้นฐานเหมือนกันส่วนใหญ่จะเข้าใจกลไกการหมดเวลา หากการล็อคได้รับเป็นครั้งแรกวิธี DoacquiresharedNanos จะถูกเรียกและเวลาหมดเวลาจะถูกส่งผ่านหลังจากเข้าสู่วิธีการล็อคจะได้รับอีกครั้งตามสถานการณ์ หากการล็อคล้มเหลวอีกครั้งเธรดจะต้องพิจารณาว่าถูกระงับ ในเวลานี้เราจะพิจารณาว่าเวลาหมดเวลามากกว่าเวลาหมุนหรือไม่ ถ้าเป็นเช่นนั้นเธรดจะถูกระงับเป็นระยะเวลาหนึ่ง มิฉะนั้นเราจะพยายามรับมันต่อไป หลังจากแต่ละครั้งที่เราได้รับล็อคเราจะลบเวลาของการล็อคเพื่อรับมัน เราจะวนซ้ำเช่นนี้จนกว่าจะหมดเวลาหมดเวลา หากการล็อคยังไม่ได้รับการซื้อกิจการจะถูกยกเลิกและธงความล้มเหลวของการได้มาจะถูกส่งคืน เธรดตอบสนองต่อการขัดจังหวะเธรดตลอดระยะเวลา
4. การดำเนินการ dequeuing ของโหนดในโหมดที่ใช้ร่วมกัน
// การดำเนินงานของการปล่อยล็อค (โหมดที่ใช้ร่วมกัน) สาธารณะสุดท้ายบูลีน releaseshared (int arg) {//1.try เพื่อปล่อยล็อคถ้า (tryreaseshared (arg)) {// 2 หากการเปิดตัวสำเร็จ กลับมาจริง; } return false;} // พยายามที่จะปล่อยล็อค (โหมดที่ใช้ร่วมกัน) บูลีนที่ได้รับการป้องกัน tryreaseshared (int arg) {โยน unsupportedoperationException ใหม่ ();} // การทำงานของการปล่อยล็อค (โหมดที่ใช้ร่วมกัน) โมฆะส่วนตัว if (h! = null && h! = tail) {// รับสถานะการรอของโหนดหัว int int ws = h.waitstatus; // ถ้าสถานะของโหนดหัวคือสัญญาณก็หมายความว่ามีคนเข้าคิวในภายหลังถ้า (ws == node.signal) {// อัปเดตสถานะการรอคอยครั้งแรกของโหนดหัวเป็น 0 ถ้า (! เปรียบเทียบกัน } // ปลุกโหนดที่ตามมา unparksuccessor (h); // ถ้าสถานะของโหนดหัวคือ 0 นั่นหมายความว่าไม่มีใครเข้าคิวในภายหลังมันจะเปลี่ยนสถานะหัวเพื่อแพร่กระจาย} อื่นถ้า (ws == 0 &&! ComperEandEANDSETWAITSTATUS (H, 0, node.propagate)) {ดำเนินการต่อ; }} // ลูปสามารถแตกได้ถ้า (h == หัว) {break; -หลังจากที่เธรดเสร็จสิ้นการทำงานในห้องมันจะเรียกวิธีการ releaseshared เพื่อปล่อยล็อค ก่อนอื่นมันจะเรียกวิธี tryreleaseshared เพื่อพยายามปล่อยล็อค ตรรกะการตัดสินของวิธีนี้ถูกนำมาใช้โดยคลาสย่อย หากการเปิดตัวสำเร็จให้โทรหาวิธี Doreleaseshared เพื่อปลุกโหนดผู้สืบทอด หลังจากเดินออกจากห้องมันจะพบที่นั่งเดิม (โหนดหัว) และดูว่ามีใครทิ้งโน้ตเล็ก ๆ ไว้บนที่นั่ง (สัญญาณสถานะ) ถ้าเป็นเช่นนั้นปลุกโหนดผู้สืบทอด หากไม่มี (สถานะ 0) หมายความว่าไม่มีใครเข้าคิวในคิวแล้วสิ่งสุดท้ายที่ต้องทำก่อนออกเดินทางคือการออกไปซึ่งคือการทิ้งโน้ตเล็ก ๆ ไว้บนที่นั่ง (สถานะถูกตั้งค่าให้เผยแพร่) เพื่อบอกคนที่อยู่เบื้องหลังล็อคเพื่อรับรัฐ ความแตกต่างเพียงอย่างเดียวระหว่างกระบวนการปล่อยล็อคทั้งหมดและโหมดพิเศษคือการทำงานในขั้นตอนสุดท้ายนี้
หมายเหตุ: การวิเคราะห์ทั้งหมดข้างต้นขึ้นอยู่กับ JDK1.7 และจะมีความแตกต่างระหว่างเวอร์ชันที่แตกต่างกันผู้อ่านจำเป็นต้องให้ความสนใจ
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น