การวิเคราะห์ซอร์สโค้ด Countdownlatch - รอ () เนื้อหาเฉพาะมีดังนี้
บทความก่อนหน้านี้พูดคุยเกี่ยวกับวิธีการใช้ Countdownlatch บทความนี้จะพูดคุยเกี่ยวกับหลักการของการรอคอย () จากระดับซอร์สโค้ด
เรารู้อยู่แล้วว่าการรอคอยสามารถเก็บเธรดปัจจุบันไว้ในสถานะการปิดกั้นจนกว่าจำนวนล็อคจะเป็นศูนย์ (หรือการหยุดชะงักของเธรด)
ด้านล่างคือซอร์สโค้ด
end.await (); ↓โมฆะสาธารณะรอ () พ่น InterruptedException {sync.acquiresharedinctibly (1);}การซิงค์คือคลาสภายในของ Countdownlatch นี่คือคำจำกัดความของมัน
การซิงค์คลาสสุดท้ายแบบคงที่ส่วนตัวขยาย Abstractqueuedsynchronizer {... }มันสืบทอด AbstractqueuedSynchronizer Abstractqueuedsynchronizer คลาสนี้เป็นของคลาสที่สำคัญมากในกระทู้ Java
มันมีกรอบในการใช้การบล็อกล็อคและซิงโครไนเซอร์ที่เกี่ยวข้อง (เช่นสัญญาณเหตุการณ์ ฯลฯ ) ที่ต้องพึ่งพาคิวรอ FIFO
เดินต่อไปและข้ามไปยังคลาส AbstractqueuedSynchronizer
sync.acquiresharedinctibly (1); public Public Void สุดท้าย AcquiresharedInctibly (int arg) // Abstractqueuedsynchronizer พ่น InterruptedException {ถ้า (thread.interrupted ()) โยน InterruptedException ใหม่ (); ถ้า (tryacquireshared (arg) <0) doacquiresharedinctibly (arg);}มีการตัดสินสองครั้งที่นี่ ขั้นแรกให้ตรวจสอบว่าเธรดถูกขัดจังหวะหรือไม่จากนั้นทำการตัดสินครั้งต่อไป ที่นี่เราดูการตัดสินครั้งที่สองเป็นหลัก
ได้รับการป้องกัน int tryacquireshared (int ซื้อ) {return (getState () == 0)? 1: -1;}ควรสังเกตว่าวิธี tryacquireshared ถูกนำไปใช้ในการซิงค์
แม้ว่าจะมีการใช้งานของมันใน AbstractqueuedSynchronizer แต่การใช้งานเริ่มต้นคือการทำข้อยกเว้น
Tryacquireshare วิธีนี้ใช้เพื่อสอบถามว่าสถานะของวัตถุปัจจุบันสามารถได้รับอนุญาตให้รับการล็อคหรือไม่
เราจะเห็นว่าในการซิงค์เราส่งคืนค่า int ที่เกี่ยวข้องโดยกำหนดว่าสถานะเป็น 0
แล้วรัฐหมายถึงอะไร?
/*** สถานะการซิงโครไนซ์ */ สถานะ int ผันผวนส่วนตัว;
รหัสข้างต้นแสดงให้เห็นอย่างชัดเจนว่าสถานะแสดงถึงสถานะการซิงโครไนซ์
ควรสังเกตว่าสถานะใช้คำหลักที่ผันผวนเพื่อแก้ไข
คำหลักที่ผันผวนสามารถตรวจสอบให้แน่ใจว่าการปรับเปลี่ยนสถานะได้รับการปรับปรุงเป็นหน่วยความจำหลักทันที เมื่อเธรดอื่นจำเป็นต้องอ่านค่าใหม่จะถูกอ่านในหน่วยความจำ
นั่นคือการมองเห็นการมองเห็นของรัฐ เป็นข้อมูลล่าสุด
รัฐที่มาที่นี่คืออะไร?
ที่นี่เราต้องดูที่ตัวสร้างของ Countdownlatch
countdownlatch end = new countdownlatch (2); public Public Countdownlatch (จำนวน int) {ถ้า (นับ <0) โยน unlegalargumentException ใหม่ ("นับ <0"); this.sync = new sync (count);} ↓ sync (จำนวน int) {setState (count);}ปรากฎว่าตัวเลขในคอนสตรัคเตอร์ใช้ในการตั้งค่าสถานะ
ดังนั้นเราจึงมีสถานะ == 2 ที่นี่ Tryacquireshare return -1 เข้าด้านล่าง
doacquiresharedinctibly (arg); ↓โมฆะส่วนตัว doacquiresharedinctibly (int arg) พ่น interruptedexception {โหนดสุดท้ายโหนด = addwaiter (node.shared); บูลีนล้มเหลว = จริง; ลอง {สำหรับ (;;) {โหนดสุดท้าย p = node.predecostor (); if (p == หัว) {int r = tryacquireshared (arg); if (r> = 0) {SetheadandPropagate (Node, R); p.next = null; // Help GC ล้มเหลว = false; กลับ; }} if (ควร parkafterfterfailedacquire (p, โหนด) && parkandcheckinterrupt ()) โยน InterruptedException ใหม่ (); }} ในที่สุด {ถ้า (ล้มเหลว) CancelAcquire (โหนด); -ตกลงรหัสนี้มีความยาวเล็กน้อยและมีหลายฟังก์ชั่นที่เรียกว่า ลองดูทีละคน
โหนดคลาสใหม่จะปรากฏในบรรทัดแรก
โหนดเป็นคลาสภายในในคลาส AQS (AbstractqueuedSynchronizer) ซึ่งกำหนดโครงสร้างห่วงโซ่ ดังที่แสดงด้านล่าง
+------+prev+-----++-----+Head | - - - - - - tail +----- + +----- + +----- +
จำโครงสร้างนี้
นอกจากนี้ยังมีวิธีการในบรรทัดแรกของรหัส addwaiter (node.shared)
Addwaiter (node.shared) //node.shared หมายความว่าโหนดอยู่ในโหมดที่ใช้ร่วมกัน↓โหนดส่วนตัว addwaiter (โหมดโหนด) {โหนดโหนด = โหนดใหม่ (thread.currentthread () โหมด); // ลองเส้นทางที่รวดเร็วของ Enq; สำรองข้อมูลเต็ม enq บนโหนดความล้มเหลว pred = tail; // หางโหนดระเหยชั่วคราวส่วนตัว; if (pred! = null) {node.prev = pred; if (เปรียบเทียบ Settail (pred, node)) {pred.next = node; ส่งคืนโหนด; }} enq (โหนด); ส่งคืนโหนด;}ขั้นแรกให้สร้างโหนดและเก็บเธรดปัจจุบัน โหมดเป็นโหมดที่ใช้ร่วมกัน
หางหมายความว่าปลายคิวของคิวรอเป็นโมฆะในขณะนี้ ดังนั้น pred == null เข้าสู่ enq (โหนด);
enq (โหนด) ↓โหนดส่วนตัว enq (โหนดโหนดสุดท้าย) {สำหรับ (;;) {node t = tail; if (t == null) {// ต้องเริ่มต้นถ้า (เปรียบเทียบ (new node ())) tail = head; } else {node.prev = t; if (เปรียบเทียบ Settail (t, node)) {t.next = node; กลับ t; -หางเดียวกันเป็นโมฆะป้อนการเปรียบเทียบ
เปรียบเทียบ (NEW NODE ()) ↓/*** ฟิลด์หัว CAS ใช้โดย enq เท่านั้น */Private Final Boolean เปรียบเทียบ (อัปเดตโหนด) {return unsafe.compareandswapobject (นี่, headoffset, null, update);}นี่คือการดำเนินการ CAS หากหัวเป็นโมฆะหัวของคิวรอจะถูกตั้งค่าเป็นค่าการอัปเดตซึ่งเป็นโหนดใหม่
หาง = หัว; จากนั้นหางจะไม่ว่างในเวลานี้อีกต่อไป เข้าสู่รอบถัดไป
เวลานี้จุดแรกตัวชี้ก่อนหน้าของโหนดไปที่หางจากนั้นตั้งค่าโหนดไปที่หางผ่านการทำงานของ CAS และส่งคืนหางของคิวนั่นคือโหนด
แบบจำลองของคิวรอการเปลี่ยนแปลงดังนี้
+ ------+ PREV - -
ตกลงเมื่อคุณมาถึงที่นี่วิธีการรอคอยจะส่งกลับมันเป็นเธรดเท่ากับโหนดของเธรดปัจจุบัน
กลับไปที่ doacquiresharedinctibly (int arg) และป้อนลูปต่อไปนี้
สำหรับ (;;) {โหนดสุดท้าย p = node.predecedor (); if (p == หัว) {int r = tryacquireshared (arg); if (r> = 0) {SetheadandPropagate (Node, R); p.next = null; // Help GC ล้มเหลว = false; กลับ; }} if (ควร parkafterferfailedacquire (p, โหนด) && parkandcheckinterrupt ()) โยน InterruptedException ใหม่ ();};}ในเวลานี้สมมติว่ารัฐยังคงสูงกว่า 0 จากนั้น r <0 ในเวลานี้ดังนั้นให้ป้อนวิธีการควร parkafterferfailedacquire
ควร parkafterferfailedacquire (p, โหนด) ↓บูลีนคงที่ส่วนตัวควรควร parkafterfterfailedacquire (โหนด pred, โหนดโหนด) {int ws = pred.waitstatus; if (ws == node.signal) // สัญญาณ int สุดท้ายคงที่ = -1; / * * โหนดนี้ได้ตั้งค่าสถานะแล้วขอให้ปล่อย * เพื่อส่งสัญญาณดังนั้นจึงสามารถจอดได้อย่างปลอดภัย */ return true; if (ws> 0) { / * * รุ่นก่อนถูกยกเลิก ข้ามไปที่รุ่นก่อนและ * ระบุการลองใหม่ */ ทำ {node.prev = pred = pred.prev; } ในขณะที่ (pred.waitstatus> 0); pred.next = node; } else { / * * waitstatus ต้องเป็น 0 หรือเผยแพร่ ระบุว่าเราต้องการสัญญาณ แต่ยังไม่จอด ผู้โทรจะต้อง * ลองอีกครั้งเพื่อให้แน่ใจว่าไม่สามารถรับได้ก่อนที่จะจอดรถ */ การเปรียบเทียบ AdsetWaitStatus (pred, ws, node.signal); } return false;} ↓/*** cas waitstatus ฟิลด์ฟิลด์ของโหนด */Private Static Final Boolean finaleanDsetWaitStatus (โหนดโหนด, int คาดหวัง, การอัปเดต int) {return unsafe.compareandswapint (โหนด, waitstatusoffset, คาดหวัง, อัปเดต);}คุณจะเห็นได้ว่า Parkafterfterfailedacquire ก็ไปตลอดทางจนถึงการเปรียบเทียบ AdsetWaitStatus
PompereanDsetWaitStatus ตั้งค่า waitstatus ของ prev เป็น node.signal
Node.Signal หมายความว่าเธรดในโหนดที่ตามมาจะต้องไม่ว่าง (คล้ายกับการตื่นขึ้น) วิธีนี้ส่งคืนเท็จ
หลังจากรอบนี้โมเดลคิวจะกลายเป็นสถานะต่อไปนี้
-
เนื่องจากควรมี parkafterfatedacquire ส่งคืนเท็จเราจะไม่ดูเงื่อนไขต่อไปนี้อีกต่อไป วนวนต่อไปสำหรับ (;;);)
หากรัฐยังคงสูงกว่า 0 ให้ป้อนอีกครั้งที่ควร Parkafterfterfailedacquire
เวลานี้เพราะ Waitstatus ในหัวคือ node.signal, ควร parkafterfterfailedacquire ส่งคืนจริง
ครั้งนี้ฉันต้องดูวิธี ParkandCheckInterrupt
Private Final Boolean ParkandCheckInterrupt () {lockksupport.park (นี่); return thread.interrupted (); -ตกลงเธรดจะไม่ถูกขัดจังหวะดังนั้นกลับมาเป็นเท็จ วนวนต่อไปสำหรับ (;;);)
หากสถานะมากกว่า 0 เสมอและเธรดจะไม่ถูกขัดจังหวะก็จะอยู่ในลูปนี้เสมอ นั่นคือผู้ตัดสินที่กล่าวถึงในบทความก่อนหน้านี้ว่าพวกเขาไม่เต็มใจที่จะประกาศการสิ้นสุดของเกม
ดังนั้นภายใต้สถานการณ์ใดที่จะแตกออก? นั่นคือภายใต้สถานการณ์ใดที่ระบุว่าน้อยกว่า 0? ฉันจะอธิบายบทความถัดไป
ในการสรุปวิธีการรอคอย () จริง ๆ แล้วจะเริ่มต้นคิวเพิ่มเธรดที่ต้องรอ (สถานะ> 0) ลงในคิวและใช้ waitstatus เพื่อทำเครื่องหมายสถานะเธรดของโหนดผู้สืบทอด
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น