การวิเคราะห์ซอร์สโค้ด Countdownlatch - Countdown ()
บทความก่อนหน้านี้พูดคุยเกี่ยวกับหลักการของการรอคอย () ใน Countdownlatch จากระดับซอร์สโค้ด บทความนี้พูดถึงการนับถอยหลัง ()
โมฆะสาธารณะนับถอยหลัง () {// countdownlatch sync.releaseshared (1);} ↓ public boolean สุดท้าย releaseshared (int arg) {// aqs ถ้า (tryreaseshared (arg)) {doreleaseshared (); กลับมาจริง; } return false;} ↓ boolean boolean tryreaseshared (releases int) {//countdownlatch.sync // จำนวนการลดลง; สัญญาณเมื่อเปลี่ยนเป็นศูนย์สำหรับ (;;) {int c = getState (); ถ้า (c == 0) ส่งคืนเท็จ; int nextc = c-1; if (ComperEndSetState (C, NextC)) ส่งคืน nextC == 0; -ผ่านตัวสร้าง Countdownlatch End = New Countdownlatch (2); สถานะถูกตั้งค่าเป็น 2 ดังนั้น c == 2, nextc = 2-1
จากนั้นตั้งสถานะเป็น 1 ผ่านการดำเนินการ CAS ต่อไปนี้
ได้รับการป้องกันขั้นสุดท้าย Boolean CompereAndSetState (Int คาดหวังการอัปเดต int) {// ดูด้านล่างสำหรับการตั้งค่า Intrinsics เพื่อสนับสนุนการส่งคืนนี้ unsafe.compareandswapint (นี่, stateoffset, คาดหวัง, อัปเดต); -ในเวลานี้ NextC ไม่ใช่ 0 และส่งคืนเท็จ รอจนกว่าวิธีการนับถอยหลัง () จะเรียกว่าสองครั้ง, state == 0, nextc == 0 และส่งคืนจริงในเวลานี้
ป้อนวิธี Doreleaseshared ()
doreleaseshared (); ↓โมฆะส่วนตัว doreleaseshared () { / * * ตรวจสอบให้แน่ใจว่าการเผยแพร่แพร่กระจายแม้ว่าจะมี * อื่น ๆ ที่กำลังดำเนินการอยู่ สิ่งนี้ดำเนินการในวิธีปกติ * ในการพยายามที่จะทำให้หัวของหัวหากต้องการสัญญาณ * แต่ถ้าไม่เป็นเช่นนั้นสถานะจะถูกตั้งค่าให้เผยแพร่ไปยัง * ตรวจสอบให้แน่ใจว่าเมื่อปล่อยการแพร่กระจายจะดำเนินต่อไป * นอกจากนี้เราต้องวนลูปในกรณีที่มีการเพิ่มโหนดใหม่ * ในขณะที่เรากำลังทำสิ่งนี้ นอกจากนี้ซึ่งแตกต่างจากการใช้งานอื่น ๆ ของ * unparksuccessor เราจำเป็นต้องรู้ว่า CAS เพื่อรีเซ็ตสถานะ * ล้มเหลวหากการตรวจอีกครั้ง */ สำหรับ (;;) {node h = head; if (h! = null && h! = tail) {int ws = h.waitstatus; if (ws == node.signal) {if (! pomperendsetwaitstatus (h, node.signal, 0)) ดำเนินการต่อ; // วนซ้ำเพื่อตรวจสอบกรณี unparksuccessor (h); } อื่นถ้า (ws == 0 &&! pomperEandEANDSETWAITSTATUS (h, 0, node.propagate)) ดำเนินการต่อ; // ลูปบน CAS ที่ล้มเหลว} ถ้า (H == Head) // loop ถ้าหัวเปลี่ยนแตก; -ระลึกถึงโมเดลคิวรอในเวลานี้
- -
ในเวลานี้หัวไม่เป็นโมฆะหรือหาง waitstatus == node.signal ดังนั้นให้ป้อนคำตัดสินถ้า (! pompereandsetwaitstatus (h, node.signal, 0))
if (! ComperendsetWaitStatus (H, Node.Signal, 0)) ↓ /*** CAS WAITSTATUS ฟิลด์ฟิลด์ของโหนด */Private Static Final Boolean finaleanDsetWaitStatus (โหนดโหนด, int คาดหวัง, การอัปเดต int) {return unsafe.compareandswapint (โหนด, waitstatusoffset, คาดหวัง, อัปเดต);}การดำเนินการ CAS นี้กำหนดสถานะเป็น 0 ซึ่งหมายความว่า waitstatus ในหัวคือ 0 ในเวลานี้ แบบจำลองคิวมีดังนี้
- -
วิธีนี้ส่งคืนจริง ป้อน unparksuccessor (H);
unparksuccessor (H); ↓โมฆะส่วนตัว unparksuccessor (โหนดโหนด) { / * * หากสถานะเป็นลบ (เช่นสัญญาณที่ต้องการ) ลอง * เพื่อล้างเพื่อคาดการณ์การส่งสัญญาณ มันก็โอเคถ้าสิ่งนี้ * ล้มเหลวหรือสถานะถูกเปลี่ยนโดยรอเธรด */ int ws = node.waitstatus; if (ws <0) pompereandsetwaitstatus (Node, Ws, 0); / * * เธรดไปยัง unpark จะถูกจัดขึ้นในผู้สืบทอดซึ่งโดยปกติจะเป็น * เพียงแค่โหนดถัดไป แต่ถ้าถูกยกเลิกหรือเป็นโมฆะ * ข้ามไปข้างหลังจากหางเพื่อค้นหาผู้สืบทอดที่ไม่ได้ถูกยกเลิกจริง * */ node s = node.next; if (s == null || s.waitstatus> 0) {s = null; สำหรับ (โหนด t = tail; t! = null && t! = node; t = t.prev) ถ้า (t.waitstatus <= 0) s = t; } if (s! = null) locksupport.unpark (s.thread);}S คือโหนดผู้สืบทอดของหัวนั่นคือโหนดที่มีเธรดปัจจุบัน s! = null และ s.waitstatus == 0 ดังนั้นป้อน locksupport.unpark (s.thread);
โมฆะคงที่สาธารณะ unpark (เธรดเธรด) {ถ้า (เธรด! = null) unsafe.unpark (เธรด); -นั่นคือเธรดที่ปลดล็อคบล็อก ผู้ตัดสินได้รับอนุญาตให้เป่านกหวีด!
หลักการของการนับถอยหลัง () มีความชัดเจนมาก
ทุกครั้งที่มีการดำเนินการวิธีการนับถอยหลัง () สถานะจะลดลง 1 จนกระทั่ง state == 0 เธรดที่ถูกบล็อกในคิวจะเริ่มถูกปล่อยออกมาและเธรดในโหนดต่อมาจะถูกปล่อยออกมาตามสถานะของ waitstatus ในโหนดก่อนหน้า
ตกลงกลับไปที่คำถามของบทความก่อนหน้านี้เมื่อใดที่ลูปต่อไปนี้จะแตกออก (ลูปในวิธีการรอคอย)
สำหรับ (;;) {โหนดสุดท้าย 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 ใหม่ ();};}ในเวลานี้ state == 0 ดังนั้นให้ป้อนวิธี SetheadandPropagate
SetheadandPropagate (Node, R); ↓โมฆะส่วนตัว setheadandpropagate (โหนดโหนด, int แพร่กระจาย) {node h = head; // บันทึกหัวเก่าเพื่อตรวจสอบด้านล่าง Sethead (โหนด); / * * ลองส่งสัญญาณโหนดที่คิวต่อไปถ้า: * การแพร่กระจายถูกระบุโดยผู้โทร * หรือถูกบันทึก (เช่น h.waitstatus ก่อน * หรือหลัง sethead) โดยการดำเนินการก่อนหน้านี้ * (หมายเหตุ: สิ่งนี้ใช้การตรวจสอบการตรวจสอบของ Waitstatus สาเหตุ * การปลุกที่ไม่จำเป็น แต่เฉพาะเมื่อมีการแข่ง/การแข่งหลายครั้งดังนั้นจึงต้องการสัญญาณส่วนใหญ่ตอนนี้หรือเร็ว ๆ นี้ * ต่อไป */ if (แพร่กระจาย> 0 || h == null || h.waitstatus <0 || (h = head) == null || h.waitstatus <0) {node s = node.next; if (s == null || s.isshared ()) doreleaseshared (); }} ↓โมฆะส่วนตัว sethead (โหนดโหนด) {head = node; node.thread = null; node.prev = null;}วิธีนี้เปลี่ยนโหนดผู้สืบทอดของหัวเข้าสู่หัว หลังจากวิธีนี้โหนดถัดไปของโหนดจะถูกตั้งค่าเป็น null และโมเดลจะกลายเป็นรูปต่อไปนี้
ก่อน - -
นั่นคือหางหัวโหนดและสิ่งอื่น ๆ ถูกตั้งค่าเป็นโมฆะรอให้ GC รีไซเคิล ในเวลานี้กลับมากระโดดออกมาจากการวนรอบและคิวจะถูกล้างออก
นี่คือการสาธิตกระบวนการทั้งหมด
SetheadandPropagate (Node, R); - - เธรด = null | <---- โหนด (หาง) | CurrentThread | - - โหนด (หาง) | CurrentThread | +-------------------------------+ - -
แกนกลางของ Countdownlatch คือคิวการปิดกั้นเธรดซึ่งเป็นคิวที่สร้างขึ้นจากรายการที่เชื่อมโยงซึ่งมีเธรดและ waitstatus ที่ Waitstatus อธิบายสถานะเธรดโหนดผู้สืบทอด
รัฐเป็นธงที่สำคัญมาก เมื่อสร้างมันจะถูกตั้งค่าเป็นค่า n ที่สอดคล้องกัน ถ้า n! = 0 คิวการบล็อกจะถูกบล็อกตลอดเวลาเว้นแต่เธรดจะถูกขัดจังหวะ
ทุกครั้งที่มีการใช้วิธีการนับถอยหลัง () จะใช้ state-1 และวิธีการรอคอย () จะใช้เพื่อเพิ่มเธรดที่เรียกวิธีการไปยังคิวการบล็อกจนกระทั่ง state == 0 และไม่สามารถปล่อยเธรดได้
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น