ในการเรียนรู้การเขียนโปรแกรมพร้อมกันของ Java เราต้องเรียนรู้เกี่ยวกับแพ็คเกจ java.util.concurrent มีคลาสเครื่องมือพร้อมกันมากมายที่เรามักจะใช้ภายใต้แพ็คเกจนี้เช่น: reentrantlock, Countdownlatch, Cyclicbarrier, semaphore ฯลฯ การใช้งานพื้นฐานของคลาสเหล่านี้ทั้งหมดขึ้นอยู่กับคลาส Abstractqueuedynchronizer ซึ่งแสดงความสำคัญของชั้นเรียนนี้ ดังนั้นในซีรี่ส์พร้อมกันของ Java ฉันได้วิเคราะห์คลาส Abstractqueuedsynchronizer เป็นครั้งแรก เนื่องจากคลาสนี้มีความสำคัญมากกว่าและรหัสค่อนข้างยาวเพื่อวิเคราะห์ให้ละเอียดที่สุดเท่าที่จะเป็นไปได้ฉันจึงตัดสินใจใช้บทความสี่บทความเพื่อแนะนำชั้นเรียนนี้อย่างสมบูรณ์ บทความนี้เป็นบทนำสรุปเพื่อให้ผู้อ่านมีความเข้าใจเบื้องต้นเกี่ยวกับหมวดหมู่นี้ เพื่อความเรียบง่ายของการบรรยายสถานที่บางแห่งจะใช้ AQS เพื่อเป็นตัวแทนของคลาสนี้ในอนาคต
1. คลาส AbstractqueuedSynchronizer คืออะไร?
ฉันเชื่อว่าผู้อ่านหลายคนใช้ reentrantlock แต่พวกเขาไม่รู้จักการมีอยู่ของ AbstractqueuedSynchronizer ในความเป็นจริง reentrantlock ใช้การซิงค์คลาสภายในซึ่งสืบทอด Abstractqueuedsynchronizer การใช้กลไกการล็อคทั้งหมดขึ้นอยู่กับการซิงค์คลาสภายใน นอกจากนี้ยังสามารถกล่าวได้ว่าการดำเนินการของ reentrantlock ขึ้นอยู่กับคลาส Abstractqueuedsynchronizer ในทำนองเดียวกันคลาส Countdownlatch, CyclicBarrier และ Semaphore ยังใช้วิธีเดียวกันเพื่อใช้การควบคุมล็อคของตนเอง จะเห็นได้ว่า AbstractqueuedSynchronizer เป็นรากฐานที่สำคัญของชั้นเรียนเหล่านี้ ดังนั้นสิ่งที่นำไปใช้ภายใน AQS เพื่อให้คลาสเหล่านี้ทั้งหมดขึ้นอยู่กับมัน? อาจกล่าวได้ว่า AQS ให้โครงสร้างพื้นฐานสำหรับคลาสเหล่านี้นั่นคือมันมีการล็อครหัสผ่าน หลังจากคลาสเหล่านี้มีการล็อครหัสผ่านพวกเขาสามารถตั้งรหัสผ่านของการล็อครหัสผ่านด้วยตนเอง นอกจากนี้ AQS ยังมีพื้นที่คิวและอาจารย์สอนด้าย เรารู้ว่าเธรดเป็นเหมือนคนป่าเถื่อนดั้งเดิม พวกเขาไม่รู้ว่าจะสุภาพอย่างไร พวกเขาจะรีบไปรอบ ๆ ดังนั้นคุณต้องสอนมันทีละขั้นตอนบอกมันเมื่อต้องเข้าคิวที่ไหนที่จะเข้าคิวสิ่งที่ต้องทำก่อนเข้าคิวและจะทำอย่างไรหลังจากเข้าคิว งานการศึกษาทั้งหมดเหล่านี้เสร็จสมบูรณ์โดย AQS สำหรับคุณ หัวข้อที่ได้รับการศึกษาจากมันกลายเป็นอารยธรรมและสุภาพมากและไม่ได้เป็นคนป่าเถื่อนดั้งเดิมอีกต่อไป ดังนั้นในอนาคตเราจำเป็นต้องจัดการกับหัวข้ออารยธรรมเหล่านี้เท่านั้น ไม่เคยติดต่อกับเธรดดั้งเดิมมากเกินไป!
2. เหตุใด AbstractqueuedSynchronizer จึงมีการล็อครหัสผ่าน?
// โหนดหัวของคิวการซิงโครไนซ์คิวหัวโหนดระเหยชั่วคราวชั่วคราว; // โหนดหางของคิวการซิงโครไนซ์คิวส่วนตัวชั่วคราวโหนดระเหยชั่วคราวโหนด; // สถานะ int ผันผวนส่วนตัว; // รับสถานะการซิงโครไนซ์การป้องกันขั้นสุดท้าย int int getState () {return state;} // set synchronization state protected sytstate (int newstate) {state = newState {return unsafe.compareandswapint (นี่, stateoffset, คาดหวัง, อัปเดต);}รหัสข้างต้นแสดงรายการตัวแปรสมาชิกทั้งหมดของ AQS คุณจะเห็นว่ามีตัวแปรสมาชิกเพียงสามตัวของ AQs คือการอ้างอิงโหนดหัวคิวการซิงโครไนซ์การอ้างอิงโหนดการซิงโครไนซ์คิวหางคิวและสถานะการซิงโครไนซ์ โปรดทราบว่าตัวแปรสมาชิกทั้งสามได้รับการแก้ไขด้วยคำหลักที่ผันผวนซึ่งทำให้มั่นใจได้ว่าหลายเธรดจะแก้ไขได้ว่าเป็นหน่วยความจำที่มองเห็นได้ แกนกลางของทั้งคลาสคือสถานะการซิงโครไนซ์นี้ คุณจะเห็นว่าสถานะการซิงโครไนซ์เป็นตัวแปรประเภท INT คุณสามารถพิจารณาสถานะการซิงโครไนซ์นี้เป็นล็อครหัสผ่านและยังเป็นล็อครหัสผ่านล็อคจากห้อง ค่าเฉพาะของสถานะเทียบเท่ากับรหัสผ่านที่ควบคุมการเปิดและปิดการล็อครหัสผ่าน แน่นอนรหัสผ่านของล็อคนี้จะถูกกำหนดโดยแต่ละคลาสย่อย ตัวอย่างเช่นใน reentrantlock สถานะเท่ากับ 0 หมายความว่าการล็อคเปิดอยู่มากกว่า 0 หมายถึงการล็อคถูกล็อคและในเซมาฟอร์รัฐที่สูงกว่า 0 หมายความว่าการล็อคเปิดอยู่และสถานะเท่ากับ 0 หมายความว่าล็อคถูกล็อค
3. พื้นที่คิวของ AbstractqueuedSynchronizer ถูกนำไปใช้อย่างไร?
จริงๆแล้วมีสองพื้นที่คิวภายใน AbstractqueuedSynchronizer หนึ่งคือคิวแบบซิงโครนัสและอีกอันหนึ่งเป็นคิวแบบมีเงื่อนไข ดังที่เห็นได้จากรูปด้านบนมีเพียงคิวการซิงโครไนซ์เพียงหนึ่งคิวในขณะที่สามารถมีคิวเงื่อนไขได้หลายคิว โหนดของคิวแบบซิงโครนัสถือการอ้างอิงไปยังโหนดด้านหน้าและด้านหลังตามลำดับในขณะที่โหนดของคิวเงื่อนไขมีการอ้างอิงเพียงหนึ่งเดียวกับโหนดผู้สืบทอด ในรูป T หมายถึงเธรด แต่ละโหนดมีเธรด หลังจากเธรดไม่สามารถรับล็อคได้แล้วจะเข้าสู่คิวการซิงโครไนซ์เป็นคิวก่อน หากคุณต้องการเข้าสู่คิวแบบมีเงื่อนไขเธรดจะต้องถือล็อค ถัดไปมาดูโครงสร้างของแต่ละโหนดในคิว
// โหนดของคิวซิงโครนัสเป็นโหนดคลาสสุดท้ายแบบคงที่ {โหนดสุดท้ายคงที่แชร์ = new node (); // เธรดปัจจุบันถือล็อคในโหมดที่ใช้ร่วมกันคงที่โหนดสุดท้ายพิเศษ = null; // เธรดปัจจุบันถือล็อคในโหมดพิเศษคงที่ int สุดท้ายถูกยกเลิก = 1; // โหนดปัจจุบันได้ยกเลิกสัญญาณ int สุดท้ายของล็อคคงที่ = -1; // เธรดของโหนดผู้สืบทอดจำเป็นต้องเรียกใช้เงื่อนไข int สุดท้ายคงที่ = -2; // โหนดปัจจุบันถูกจัดคิวในคิวแบบคงที่คงที่ int final prepagate = -3; // โหนดที่ตามมาสามารถได้รับการล็อค Int Int Waitstatus โดยตรง // แสดงถึงสถานะการรอคอยของโหนดระเหยของโหนดปัจจุบันก่อนหน้านี้; // แสดงถึงโหนดไปข้างหน้าในโหนดการระเหยของคิวการซิงโครไนซ์ต่อไป // แสดงถึงโหนดผู้สืบทอดในเธรดการผันผวนของคิวการซิงโครไนซ์ // เธรดที่ถือโดยโหนดปัจจุบันหมายถึงโหนด Nextwaiter; // แสดงถึงโหนดผู้สืบทอดในคิวแบบมีเงื่อนไข // เป็นสถานะโหนดปัจจุบันในโหมดที่ใช้ร่วมกันขั้นสุดท้ายบูลีน isshared () {return nextwaiter == แชร์; } // ส่งคืนโหนดไปข้างหน้าของโหนดสุดท้ายโหนดสุดท้าย () พ่น nullpointerexception {node p = prev; if (p == null) {โยน nullpointerexception ใหม่ (); } else {return p; }} // constructor 1 node () {} // constructor 2 ตัวสร้างนี้ใช้โดยโหนดเริ่มต้น (เธรดเธรดโหมดโหนด) {// โปรดทราบว่าโหมดการถือครองจะถูกกำหนดให้กับ nextwaiter this.nextwaiter = โหมด; this.thread = เธรด; } // constructor 3, โหนดเท่านั้น (เธรดเธรด, int waitstatus) ใช้ในคิวเงื่อนไข {this.waitstatus = waitstatus; this.thread = เธรด; -โหนดแสดงถึงโหนดในคิวการซิงโครไนซ์และคิวแบบมีเงื่อนไข มันเป็นคลาสภายในของ AbstractqueuedSynchronizer Node มีคุณลักษณะมากมายเช่นโหมดการถือครองสถานะรอการจัดลำดับก่อนและผู้สืบทอดในคิวแบบซิงโครนัสและการอ้างอิงผู้สืบทอดในคิวเงื่อนไข ฯลฯ คิวการซิงโครไนซ์และคิวเงื่อนไขถือได้ว่าเป็นพื้นที่คิวแต่ละโหนดถือเป็นที่นั่งในคิว เมื่อแขกมาครั้งแรกพวกเขาจะเคาะประตูเพื่อดูว่าล็อคเปิดอยู่หรือไม่ หากการล็อคไม่เปิดพวกเขาจะไปที่พื้นที่คิวเพื่อรวบรวมหมายเลขแผ่นประกาศว่าพวกเขาต้องการที่จะถือล็อคอย่างไรและในที่สุดก็คิวขึ้นในตอนท้ายของคิว
4 วิธีที่จะเข้าใจโหมดพิเศษและโหมดการแชร์?
ดังที่ได้กล่าวไว้ก่อนหน้านี้แขกแต่ละคนจะได้รับแผ่นหมายเลขก่อนเข้าคิวและประกาศว่าเขาต้องการล็อค วิธีการครอบครองล็อคแบ่งออกเป็นโหมดพิเศษและโหมดการแชร์ ดังนั้นคุณจะเข้าใจโหมดพิเศษและโหมดการแชร์ได้อย่างไร? ฉันไม่พบการเปรียบเทียบที่ดีจริงๆ คุณสามารถนึกถึงห้องน้ำสาธารณะ ผู้ที่มีโหมดพิเศษมีการครอบงำมากขึ้น ฉันไม่เข้าไปข้างในเมื่อฉันเข้ามาฉันไม่อนุญาตให้คนอื่นเข้าไปข้างในฉันครอบครองห้องน้ำทั้งหมดเพียงลำพัง ผู้คนในโหมดการแชร์นั้นไม่ได้มีความเฉพาะเจาะจง เมื่อพวกเขาพบว่าห้องน้ำใช้งานได้แล้วมันจะไม่นับถ้ามันเข้ามาด้วยตัวเอง พวกเขายังต้องถามผู้คนที่อยู่เบื้องหลังว่าพวกเขาใช้มันด้วยกันหรือไม่ หากผู้คนที่อยู่เบื้องหลังไม่ได้ใช้มันเข้าด้วยกันไม่จำเป็นต้องเข้าคิว ทุกคนจะไปด้วยกัน แน่นอนถ้าคนที่อยู่เบื้องหลังพวกเขาพวกเขาจะต้องอยู่ในคิวและเข้าคิวต่อไป
5 จะเข้าใจสถานะการรอของโหนดได้อย่างไร?
นอกจากนี้เรายังเห็นว่าแต่ละโหนดมีสถานะรอซึ่งแบ่งออกเป็นสี่สถานะ: ยกเลิกสัญญาณเงื่อนไขและการแพร่กระจาย สถานะการรอคอยนี้ถือได้ว่าเป็นสัญญาณที่แขวนอยู่ถัดจากที่นั่งระบุสถานะการรอคอยของบุคคลในที่นั่งปัจจุบัน สถานะของแบรนด์นี้ไม่เพียง แต่สามารถแก้ไขได้ด้วยตัวเองเท่านั้น แต่ยังสามารถแก้ไขได้ ตัวอย่างเช่นเมื่อเธรดนี้ได้วางแผนที่จะยอมแพ้ในระหว่างคิวมันจะตั้งเครื่องหมายบนที่นั่งให้ยกเลิกเพื่อให้คนอื่นสามารถล้างออกจากคิวหากพวกเขาเห็นมัน อีกสถานการณ์หนึ่งคือเมื่อด้ายกำลังจะหลับไปในที่นั่งมันก็กลัวว่ามันจะนอนหลับมากเกินไปดังนั้นมันจะเปลี่ยนป้ายในตำแหน่งด้านหน้าเพื่อส่งสัญญาณเพราะทุกคนจะกลับไปที่ที่นั่งก่อนออกจากคิวเพื่อดู หากเห็นว่าสถานะบนสัญญาณคือสัญญาณมันจะปลุกบุคคลต่อไป โดยมั่นใจว่าแบรนด์ที่อยู่ด้านหน้าคือสัญญาณด้ายปัจจุบันจะนอนหลับอย่างสงบ สถานะเงื่อนไขบ่งชี้ว่าเธรดจะถูกคิวในคิวแบบมีเงื่อนไข สถานะการแพร่กระจายเตือนเธรดที่ตามมาเพื่อรับล็อคโดยตรง สถานะนี้ใช้ในโหมดที่ใช้ร่วมกันเท่านั้นและจะมีการหารือในภายหลังเมื่อพูดถึงโหมดที่ใช้ร่วมกันแยกกัน
6. การดำเนินการใดจะดำเนินการเมื่อโหนดเข้าสู่คิวการซิงโครไนซ์?
// การทำงานของโหนด enqueue กลับไปที่โหนดส่วนตัวโหนดก่อนหน้า enq (โหนดโหนดสุดท้าย) {สำหรับ (;;) {// รับการอ้างอิงไปยังโหนดหางของโหนดคิวการซิงโครไนซ์คิว t = หาง; // ถ้าโหนดหางว่างเปล่าหมายความว่าคิวการซิงโครไนซ์ไม่ได้เริ่มต้นถ้า (t == null) {// เริ่มต้นคิวการซิงโครไนซ์ถ้า (เปรียบเทียบ (NEW NODE ())) {tail = head; }} else {// 1 ชี้ไปที่โหนดหางปัจจุบัน Node.prev = t; // 2. ตั้งค่าโหนดปัจจุบันเป็นโหนดหางถ้า (เปรียบเทียบกับ (t, โหนด)) {// 3 ชี้ผู้สืบทอดของโหนดหางเก่าไปยังโหนดหางใหม่ t.next = โหนด; // ทางออกเดียวของการวนรอบกลับ t; -โปรดทราบว่าการดำเนินการ enqueue ใช้ลูปที่ตายแล้ว เฉพาะเมื่อมีการเพิ่มโหนดลงในหางของคิวการซิงโครไนซ์แล้วจะถูกส่งคืน ผลที่ได้คือโหนดหางเดิมของคิวการซิงโครไนซ์ รูปต่อไปนี้แสดงกระบวนการดำเนินการทั้งหมด
ผู้อ่านจำเป็นต้องให้ความสนใจกับลำดับของการเพิ่มโหนดหางซึ่งแบ่งออกเป็นสามขั้นตอน: ชี้ไปที่โหนดหาง, CAS เปลี่ยนโหนดหางและชี้ผู้สืบทอดของโหนดหางเก่าไปยังโหนดปัจจุบัน ในสภาพแวดล้อมที่เกิดขึ้นพร้อมกันทั้งสามขั้นตอนนี้อาจไม่รับประกันว่าจะเสร็จสมบูรณ์ ดังนั้นในการดำเนินการของการล้างโหนดที่ถูกยกเลิกทั้งหมดในคิวการซิงโครไนซ์เพื่อค้นหาโหนดในสถานะที่ไม่ใช่พลเมืองจึงไม่ผ่านจากด้านหน้าไปด้านหน้า แต่จากด้านหน้าไปด้านหน้า นอกจากนี้เมื่อแต่ละโหนดเข้าสู่คิวสถานะการรอคอยคือ 0 เฉพาะเมื่อเธรดของโหนดที่ตามมาจะต้องถูกระงับสถานะการรอของโหนดก่อนหน้าจะเปลี่ยนเป็นสัญญาณ
หมายเหตุ: การวิเคราะห์ทั้งหมดข้างต้นขึ้นอยู่กับ JDK1.7 และจะมีความแตกต่างระหว่างเวอร์ชันที่แตกต่างกันผู้อ่านจำเป็นต้องให้ความสนใจ
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น