ในบทความก่อนหน้านี้ "Java Concurrency Series [1] ----- การวิเคราะห์ซอร์สโค้ด Absstractqueuedynchronizer" เราได้แนะนำแนวคิดพื้นฐานบางประการของ AbstractqueuedSynchronizer ส่วนใหญ่พูดถึงวิธีการใช้ AQS ของ AQS การทำความเข้าใจและการควบคุมเนื้อหาเหล่านี้เป็นกุญแจสำคัญในการอ่านซอร์สโค้ด AQS ในภายหลังดังนั้นขอแนะนำให้ผู้อ่านอ่านบทความก่อนหน้าของฉันก่อนจากนั้นมองย้อนกลับไปที่บทความนี้เพื่อทำความเข้าใจ ในบทความนี้เราจะแนะนำวิธีที่โหนดป้อนคิวคิวการซิงโครไนซ์ในโหมดพิเศษและการดำเนินการใดที่จะดำเนินการก่อนออกจากคิวการซิงโครไนซ์ AQS ให้สามวิธีในการรับการล็อคในโหมดพิเศษและโหมดที่ใช้ร่วมกัน: การได้มาซึ่งการขัดจังหวะเธรดที่ไม่ตอบสนองการตอบสนองการตอบสนองของเธรดการตอบสนองและการตั้งค่าการหมดเวลา ขั้นตอนโดยรวมของวิธีการทั้งสามนี้จะเหมือนกันโดยมีเพียงไม่กี่ส่วนเท่านั้นดังนั้นหากคุณเข้าใจวิธีหนึ่งและดูการใช้วิธีอื่นคุณจะคล้ายกัน ในบทความนี้ฉันจะมุ่งเน้นไปที่วิธีการได้มาซึ่งไม่ตอบสนองต่อการขัดจังหวะเธรดและอีกสองวิธีจะพูดถึงความไม่สอดคล้องกัน
1. จะรับการล็อคที่มีการขัดจังหวะเธรดที่ไม่ตอบสนองได้อย่างไร?
// ไม่ตอบสนองต่อการได้มาซึ่งวิธีการขัดจังหวะ (โหมดพิเศษ) โมฆะสุดท้ายของสาธารณะได้รับ (int arg) {ถ้า (! tryacquire (arg) && acquirequeued (addwaiter (node.exclusive), arg) {selfinterrupt (); -แม้ว่ารหัสด้านบนจะดูเรียบง่าย แต่ก็ดำเนินการ 4 ขั้นตอนที่แสดงในรูปด้านล่างตามลำดับ ด้านล่างเราจะสาธิตและวิเคราะห์ทีละขั้นตอน
ขั้นตอนที่ 1:! Tryacquire (ARG)
// ลองรับการล็อค (โหมดพิเศษ) บูลีน tryacquire (int arg) {โยน unsupportedoperationException ใหม่ ();}ในเวลานี้มีคนมาและเขาพยายามเคาะประตูก่อน หากเขาพบว่าประตูไม่ได้ล็อค (Tryacquire (arg) = true) เขาจะเข้าไปโดยตรง หากคุณพบว่าประตูถูกล็อค (tryacquire (arg) = false) จากนั้นทำตามขั้นตอนต่อไป วิธี Tryacquire นี้กำหนดว่าจะเปิดล็อคเมื่อปิดและเมื่อปิดล็อค วิธีนี้จะต้องถูกเขียนทับโดย subclasses และเขียนตรรกะการตัดสินภายใน
ขั้นตอนที่ 2: AddWaiter (node.exclusive)
// ห่อเธรดปัจจุบันลงในโหนดและเพิ่มลงในหางของคิวการซิงโครไนซ์โหนดส่วนตัว AddWaiter (โหมดโหนด) {// ระบุโหมดที่ถือโหนดล็อค Node = โหนดใหม่ (thread.currentThread () โหมด); // รับการอ้างอิงของโหนดหางของโหนดการซิงโครไนซ์คิว pred = หาง; // ถ้าโหนดหางไม่ว่างเปล่าหมายความว่าคิวการซิงโครไนซ์มีโหนดอยู่แล้วถ้า (pred! = null) {// 1 ชี้ไปที่โหนดหางปัจจุบัน Node.prev = pred; // 2. ตั้งค่าโหนดปัจจุบันเป็นโหนดหางถ้า (เปรียบเทียบกับ (pred, pred, node)) {// 3 ชี้ผู้สืบทอดของโหนดหางเก่าไปยังโหนดหางใหม่ pred.next = โหนด; ส่งคืนโหนด; }} // มิฉะนั้นหมายความว่าคิวการซิงโครไนซ์ยังไม่ได้เริ่มต้น enq (โหนด); ส่งคืนโหนด;} // node enqueue private node enq (โหนดสุดท้ายโหนด) {สำหรับ (;;) {// รับการอ้างอิงของโหนดหางของโหนดการซิงโครไนซ์คิว t = หาง; // ถ้าโหนดหางว่างเปล่าหมายความว่าคิวการซิงโครไนซ์ไม่ได้เริ่มต้นถ้า (t == null) {// เริ่มต้นคิวการซิงโครไนซ์ถ้า (เปรียบเทียบ (NEW NODE ())) {tail = head; }} else {// 1 ชี้ไปที่โหนดหางปัจจุบัน Node.prev = t; // 2. ตั้งค่าโหนดปัจจุบันเป็นโหนดหางถ้า (เปรียบเทียบกับ (t, โหนด)) {// 3 ชี้ผู้สืบทอดของโหนดหางเก่าไปยังโหนดหางใหม่ t.next = โหนด; กลับ t; -การดำเนินการในขั้นตอนนี้บ่งชี้ว่าครั้งแรกที่การล็อคการเข้าซื้อกิจการล้มเหลวดังนั้นบุคคลนั้นจะได้รับบัตรหมายเลขสำหรับตัวเองและเข้าสู่พื้นที่คิวเพื่อเข้าคิว เมื่อได้รับบัตรหมายเลขเขาจะประกาศว่าเขาต้องการครอบครองห้อง (โหมดพิเศษหรือโหมดการแชร์) โปรดทราบว่าเขาไม่ได้นั่งลงและพักผ่อนในเวลานี้ (วางตัวเอง)
ขั้นตอนที่ 3: Acquirequeued (addwaiter (node.exclusive), arg)
// การรับล็อคในลักษณะที่ไม่หยุดยั้ง (โหมดพิเศษ) บูลีนสุดท้ายได้รับ (โหนดโหนดสุดท้าย, int arg) {บูลีนล้มเหลว = true; ลอง {บูลีนขัดจังหวะ = false; สำหรับ (;;) {// ได้รับการอ้างอิงของโหนดก่อนหน้าของโหนดสุดท้ายโหนดสุดท้าย p = node.predepector (); // ถ้าโหนดปัจจุบันเป็นโหนดแรกของคิวการซิงโครไนซ์ให้ลองรับล็อคถ้า (p == หัว && tryacquire (arg)) {// ตั้งค่าโหนดที่กำหนดเป็นโหนดหัว sethead (โหนด); // เพื่อช่วยเก็บขยะให้ล้างผู้สืบทอดของโหนดหัวก่อนหน้า p.next = null; // ตั้งค่าสถานะการได้มาที่ประสบความสำเร็จล้มเหลว = false; // ส่งคืนสถานะที่ถูกขัดจังหวะการวนซ้ำทั้งหมดจะถูกดำเนินการที่นี่การส่งคืนทางออกถูกขัดจังหวะ; } // มิฉะนั้นหมายความว่าสถานะการล็อคยังไม่พร้อมใช้งาน ในเวลานี้ตรวจสอบว่าสามารถระงับเธรดปัจจุบัน // หากผลลัพธ์เป็นจริงเธรดปัจจุบันสามารถระงับได้หรือไม่มิฉะนั้นลูปจะดำเนินต่อไปในช่วงเวลานี้เธรดจะไม่ตอบสนองต่อการขัดจังหวะหาก }}} ในที่สุด {// ตรวจสอบให้แน่ใจว่าได้ยกเลิกการได้มาหาก (ล้มเหลว) {CancelAcquire (โหนด); }}} // ตัดสินว่าสามารถระงับบูลีนสแตติกส่วนตัวของโหนดปัจจุบันได้ควร Parkafterfterfailedacquire (โหนด pred, โหนดโหนด) {// รับสถานะการรอของโหนดไปข้างหน้า int ws = pred.waitstatus; // หากสถานะโหนดไปข้างหน้าเป็นสัญญาณหมายความว่าโหนดไปข้างหน้าจะปลุกโหนดปัจจุบันดังนั้นโหนดปัจจุบันสามารถระงับได้อย่างปลอดภัยถ้า (ws == node.signal) {return true; } if (ws> 0) {// การดำเนินการต่อไปนี้คือการทำความสะอาดโหนดไปข้างหน้าที่ถูกยกเลิกทั้งหมดในคิวการซิงโครไนซ์ทำ {node.prev = pred = pred.prev; } ในขณะที่ (pred.waitstatus> 0); pred.next = node; } else {// ถึงจุดสิ้นสุดนี้หมายความว่าสถานะของโหนดไปข้างหน้าไม่ใช่สัญญาณและมีแนวโน้มที่จะเท่ากับ 0 ด้วยวิธีนี้โหนดไปข้างหน้าจะไม่ปลุกโหนดปัจจุบัน//ดังนั้นโหนดปัจจุบันจะต้องตรวจสอบให้แน่ใจว่าสถานะของโหนดไปข้างหน้า } return false;} // ระงับกระทู้ปัจจุบัน Boolean Boolean ParkandCheckInterrupt () {locksupport.park (นี่); return thread.interrupted ();}หลังจากได้รับสัญญาณหมายเลขเขาจะใช้วิธีนี้ทันที เมื่อโหนดเข้าสู่พื้นที่คิวเป็นครั้งแรกมีสองสถานการณ์ หนึ่งคือเขาพบว่าคนที่อยู่ข้างหน้าเขาออกจากที่นั่งและเข้าไปในห้องดังนั้นเขาจะไม่นั่งลงและพักผ่อนและจะเคาะประตูอีกครั้งเพื่อดูว่าเด็กเสร็จหรือไม่ หากคนข้างในเพิ่งจะเสร็จสิ้นเขาจะรีบเข้ามาโดยไม่เรียกตัวเอง มิฉะนั้นเขาจะต้องพิจารณานั่งลงและพักผ่อนสักพัก แต่เขาก็ยังกังวลอยู่ ถ้าไม่มีใครเตือนเขาหลังจากที่เขานั่งลงและหลับไป? เขาทิ้งโน้ตเล็ก ๆ ไว้บนที่นั่งของชายคนนั้นไว้ข้างหน้าเพื่อให้คนที่ออกมาจากข้างในสามารถปลุกเขาได้หลังจากเห็นโน้ต อีกสถานการณ์หนึ่งคือเมื่อเขาเข้าไปในพื้นที่คิวและพบว่ามีหลายคนเข้าคิวอยู่ข้างหน้าเขาเขาสามารถนั่งลงได้ซักพัก แต่ก่อนหน้านั้นเขาจะยังคงทิ้งบันทึกไว้บนที่นั่งของคนข้างหน้า (เขาหลับไปแล้วในเวลานี้) เมื่อทุกอย่างเสร็จสิ้นเขาก็นอนหลับอย่างสงบสุข โปรดทราบว่าเราเห็นว่าทั้งหมดสำหรับลูปมีทางออกเพียงครั้งเดียวนั่นคือมันสามารถออกไปได้หลังจากที่เธรดได้รับการล็อคสำเร็จ ก่อนที่จะได้รับการล็อคมันจะถูกแขวนไว้ในวิธี parkandcheckinterrupt () ของการวนรอบเสมอ หลังจากตื่นเธรดแล้วมันก็ยังคงดำเนินการสำหรับการวนรอบจากสถานที่นี้
ขั้นตอนที่ 4: selfinterrupt ()
// เธรดปัจจุบันจะขัดจังหวะตัวเองเป็นโมฆะแบบคงที่ส่วนตัว selfinterrupt () {thread.currentthread () interrupt (); -เนื่องจากเธรดทั้งหมดด้านบนถูกแขวนไว้ในวิธี ParkandCheckInterrupt () ของการวนรอบจึงไม่ตอบสนองต่อการขัดจังหวะเธรดรูปแบบใด ๆ ก่อนที่จะได้รับสำเร็จ เฉพาะเมื่อเธรดได้รับการล็อคสำเร็จและออกมาจากการวนรอบจะตรวจสอบว่ามีคนขอให้ขัดจังหวะเธรดในช่วงเวลานี้หรือไม่ ถ้าเป็นเช่นนั้นให้เรียกเมธอด selfinterrupt () เพื่อแขวนเอง
2. จะรับการล็อคเพื่อตอบสนองต่อการขัดจังหวะเธรดได้อย่างไร?
// การรับล็อคในโหมดขัดจังหวะ (โหมดพิเศษ) โมฆะส่วนตัว doacquireinctibly (int arg) พ่น InterruptedException {// การห่อเธรดปัจจุบันลงในโหนดและเพิ่มลงในคิวการซิงโครไนซ์โหนดสุดท้าย Node = AddWaiter (Node.ExClusive); บูลีนล้มเหลว = จริง; ลอง {สำหรับ (;;) {// การรับโหนดสุดท้ายโหนดสุดท้าย p = node.predecostor (); // ถ้า p เป็นโหนดหัวดังนั้นเธรดปัจจุบันจะพยายามรับล็อคอีกครั้งถ้า (p == หัว && tryacquire (arg)) {sethead (โหนด); p.next = null; // Help GC ล้มเหลว = false; // ผลตอบแทนผลตอบแทนหลังจากได้รับการล็อคสำเร็จ } // หากเป็นไปตามเงื่อนไขเธรดปัจจุบันจะถูกระงับ ในเวลานี้การขัดจังหวะจะได้รับการตอบรับและข้อยกเว้นจะถูกโยนลงถ้า (ควร parkafterferfailedacquire (p, โหนด) && parkandcheckinterrupt ()) {// หากเธรดถูกปลุก โยน InterruptedException ใหม่ (); }}} ในที่สุด {ถ้า (ล้มเหลว) {cancelacquire (โหนด); -วิธีการขัดจังหวะเธรดการตอบสนองและวิธีการขัดจังหวะเธรดที่ไม่ตอบสนองจะเหมือนกันในกระบวนการรับล็อค ความแตกต่างเพียงอย่างเดียวคือหลังจากที่เธรดตื่นขึ้นมาจากวิธี parkandcheckinterrupt มันจะตรวจสอบว่าเธรดถูกขัดจังหวะหรือไม่ ถ้าเป็นเช่นนั้นมันจะทำให้เกิดข้อยกเว้น InterruptedException แทนที่จะตอบสนองต่อการล็อคการขัดจังหวะการขัดจังหวะเธรดจะตั้งค่าสถานะการขัดจังหวะหลังจากได้รับคำขอขัดจังหวะและจะไม่สิ้นสุดวิธีการรับล็อคในปัจจุบันทันที มันจะไม่ตัดสินใจว่าจะแขวนตัวเองตามสถานะการขัดจังหวะหลังจากโหนดได้รับการล็อคสำเร็จหรือไม่
3. จะกำหนดเวลาหมดเวลาเพื่อรับล็อคได้อย่างไร?
// การรับล็อคด้วยการหมดเวลา จำกัด (โหมดพิเศษ) บูลีนส่วนตัว doacquirenanos (int arg, long nanostimeout) พ่น InterruptedException {// การรับระบบปัจจุบันเวลาสุดท้าย = System.nanotime (); // การห่อเธรดปัจจุบันลงในโหนดและเพิ่มลงในคิวการซิงโครไนซ์โหนดสุดท้ายโหนด = addWaiter (node.exclusive); บูลีนล้มเหลว = จริง; ลอง {สำหรับ (;;) {// การรับโหนดสุดท้ายโหนดสุดท้าย p = node.predecostor (); // ถ้าโหนดก่อนหน้าเป็นโหนดหัวดังนั้นเธรดปัจจุบันจะพยายามรับล็อคอีกครั้งถ้า (p == หัว && tryacquire (arg)) {// อัปเดตโหนดหัว sethead (โหนด); p.next = null; ล้มเหลว = เท็จ; กลับมาจริง; } // เมื่อใช้เวลาหมดเวลาแล้วให้ออกจากลูปโดยตรงถ้า (nanostimeout <= 0) {return false; } // หากเวลาหมดเวลามากกว่าเวลาหมุนหลังจากนั้นหลังจากตัดสินว่าสามารถระงับเธรดได้เธรดจะถูกระงับเป็นระยะเวลาหนึ่งถ้า (ควร parkafterfterferedacquire (p, โหนด) && nanostimeout> spinfortimeOutThreshold) {// มีเธรดปัจจุบัน } // รับเวลาปัจจุบันของระบบยาวตอนนี้ = system.nanotime (); // เวลาหมดเวลาจะถูกลบออกจากช่วงเวลาของการล็อคการได้มา Nanostimeout - = ตอนนี้ - ล่าสุด; // อัปเดตล่าสุดอีกครั้งล่าสุด = ตอนนี้; // ข้อยกเว้นจะถูกโยนลงเมื่อได้รับคำขอขัดจังหวะระหว่างการได้มาซึ่งล็อคถ้า (thread.interrupted ()) {โยน InterruptedException ใหม่ (); }}} ในที่สุด {ถ้า (ล้มเหลว) {cancelacquire (โหนด); -การตั้งค่าการซื้อเวลาหมดเวลาจะได้รับการล็อคก่อน หลังจากการได้มาครั้งแรกล้มเหลวมันจะขึ้นอยู่กับสถานการณ์ หากเวลาหมดเวลาที่เข้ามามากกว่าเวลาหมุนเธรดจะถูกระงับเป็นระยะเวลาหนึ่งมิฉะนั้นจะหมุน หลังจากแต่ละครั้งที่ล็อคได้รับเวลาหมดเวลาจะถูกลบออกจากเวลาที่ใช้ในการรับล็อค จนกว่าการหมดเวลาจะน้อยกว่า 0 หมายความว่าเวลาหมดเวลาถูกใช้ไปแล้ว จากนั้นการดำเนินการของการรับล็อคจะถูกยกเลิกและธงความล้มเหลวของการได้มาจะถูกส่งคืน โปรดทราบว่าในระหว่างกระบวนการรับล็อคด้วยเวลาหมดเวลาคุณสามารถตอบกลับคำขออินเตอร์รัปต์เธรดได้
4. เธรดจะปล่อยล็อคและออกจากคิวการซิงโครไนซ์ได้อย่างไร?
// release Lock Operation (โหมดพิเศษ) Public Final Boolean Release (int arg) {// หมุนรหัสผ่านล็อคเพื่อดูว่ามันสามารถปลดล็อคได้หรือไม่ถ้า (tryrelease (arg)) {// รับโหนดโหนดหัว h = head; // หากโหนดหัวไม่ว่างเปล่าและสถานะการรอไม่เท่ากับ 0 ปลุกโหนดผู้สืบทอดถ้า (h! = null && h.waitstatus! = 0) {// ปลุกโหนดผู้สืบทอด Unparksuccessor (H); } return true; } return false;} // ปลุกโหนดผู้สืบทอดโมฆะส่วนตัวโมฆะ unparksuccessor (โหนดโหนด) {// รับสถานะการรอของโหนดที่กำหนด int ws = node.waitstatus; // อัปเดตสถานะการรอเป็น 0 ถ้า (WS <0) {เปรียบเทียบ SEPOLEANDSETWAITSTATUS (NODE, WS, 0); } // รับโหนดที่ตามมาของโหนดโหนดที่กำหนด s = node.next; // โหนดผู้สืบทอดว่างเปล่าหรือสถานะการรอถูกยกเลิกถ้า (s == null || s.waitstatus> 0) {s = null; // จบโหนดแรกที่ไม่ได้ยกเลิกจากคิวการสำรวจย้อนหน้าไปข้างหลังสำหรับ (โหนด t = tail; t! = null && t! = node; t = t.prev) {ถ้า (t.waitstatus <= 0) {s = t; }}} // ปลุกโหนดแรกหลังจากโหนดที่กำหนดซึ่งไม่ใช่สถานะการยกเลิกถ้า (s! = null) {locksupport.unpark (s.thread); -หลังจากเธรดถือล็อคเข้าไปในห้องมันจะทำธุรกิจของตัวเอง หลังจากทำงานเสร็จแล้วมันจะปล่อยล็อคและออกจากห้อง การล็อครหัสผ่านสามารถปลดล็อคได้ด้วยวิธีการ tryrelease เรารู้ว่าวิธีการ tryrelease จะต้องถูกเขียนทับโดยคลาสย่อย กฎการใช้งานของคลาสย่อยที่แตกต่างกันนั้นแตกต่างกันซึ่งหมายความว่ารหัสผ่านที่กำหนดโดยคลาสย่อยที่แตกต่างกันนั้นแตกต่างกัน ตัวอย่างเช่นใน reentrantlock ทุกครั้งที่บุคคลในห้องพักเรียกใช้วิธี tryrelease รัฐจะลดลง 1 จนกว่าสถานะจะลดลงเป็น 0 การล็อครหัสผ่านจะถูกเปิด ลองคิดดูว่ากระบวนการนี้ดูเหมือนว่าเราจะหมุนล้อของการล็อครหัสผ่านอยู่ตลอดเวลาและจำนวนล้อจะลดลง 1 ครั้งในแต่ละครั้งที่เราหมุน Countdownlatch นั้นคล้ายกับอันนี้เล็กน้อยยกเว้นว่าไม่ใช่แค่ว่ามันเปลี่ยนเป็นคนเดียว แต่มันจะเปลี่ยนคนคนหนึ่งไปรอบ ๆ โดยมุ่งเน้นที่ความแข็งแกร่งของทุกคนในการเปิดล็อค หลังจากที่ด้ายออกจากห้องมันจะพบที่นั่งดั้งเดิมนั่นคือค้นหาโหนดหัว ดูว่ามีใครทิ้งโน้ตเล็ก ๆ ไว้บนที่นั่งหรือไม่ หากมีมันจะรู้ว่ามีคนหลับและต้องขอให้มันช่วยให้ตื่นขึ้นมาและจากนั้นมันจะตื่นขึ้นมากระทู้นั้น ถ้าไม่ได้หมายความว่าไม่มีใครรอคิวการซิงโครไนซ์ในขณะนี้และไม่มีใครต้องการให้ตื่นขึ้นมาดังนั้นมันจึงสามารถออกไปด้วยความอุ่นใจ กระบวนการข้างต้นเป็นกระบวนการของการปล่อยล็อคในโหมดพิเศษ
หมายเหตุ: การวิเคราะห์ทั้งหมดข้างต้นขึ้นอยู่กับ JDK1.7 และจะมีความแตกต่างระหว่างเวอร์ชันที่แตกต่างกันผู้อ่านจำเป็นต้องให้ความสนใจ
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น