เห็นได้ชัดว่าnotify
ตื่น (มี) หนึ่งเธรดในชุดการรอnotifyAll
ตื่นกระทู้ทั้งหมดในชุดการรอ การสนทนาต่อไปนี้ควรกำจัดข้อสงสัยใด ๆ notifyAll
ควรใช้เวลาส่วนใหญ่ หากคุณไม่แน่ใจว่าจะใช้อะไรให้ใช้notifyAll
โปรดดูคำอธิบายที่ตามมา
อ่านอย่างระมัดระวังและเข้าใจ กรุณาส่งอีเมลฉันหากคุณมีคำถามใด ๆ
ดูโปรดิวเซอร์ / ผู้บริโภค (สมมติว่าคลาส ProducerConsumer มีสองวิธี) มันแย่ (เพราะมันใช้notify
) - ใช่มันอาจใช้ได้ - แม้เวลาส่วนใหญ่ แต่มันก็อาจทำให้เกิดการหยุดชะงักได้ - เราจะเห็นว่าทำไม:
public synchronized void put(Object o) {
while (buf.size()==MAX_SIZE) {
wait(); // called if the buffer is full (try/catch removed for brevity)
}
buf.add(o);
notify(); // called in case there are any getters or putters waiting
}
public synchronized Object get() {
// Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
while (buf.size()==0) {
wait(); // called if the buffer is empty (try/catch removed for brevity)
// X: this is where C1 tries to re-acquire the lock (see below)
}
Object o = buf.remove(0);
notify(); // called if there are any getters or putters waiting
return o;
}
ประการแรก
ทำไมเราต้องใช้ขณะที่วนรอบการรอ?
เราต้องการการwhile
วนซ้ำในกรณีที่เราได้รับสถานการณ์นี้:
ผู้บริโภค 1 (C1) เข้าสู่บล็อกที่ซิงโครไนซ์และบัฟเฟอร์ว่างเปล่าดังนั้น C1 จะอยู่ในชุดการรอ (ผ่านการwait
โทร) ผู้บริโภค 2 (C2) กำลังจะเข้าสู่วิธีการทำข้อมูลให้ตรงกัน (ที่จุด Y ข้างต้น) แต่ผู้ผลิต P1 notify
ทำให้วัตถุในบัฟเฟอร์และบริการโทรภายหลัง เธรดที่รออยู่เพียง C1 เท่านั้นจึงถูกปลุกและตอนนี้พยายามรับการล็อกวัตถุอีกครั้งที่จุด X (ด้านบน)
ตอนนี้ C1 และ C2 กำลังพยายามรับการล็อกการซิงโครไนซ์ หนึ่งในนั้น (nondeterministically) ถูกเลือกและเข้าสู่วิธีการอื่นถูกบล็อก (ไม่รอ - แต่ถูกบล็อกพยายามรับการล็อกบนเมธอด) สมมติว่า C2 ได้รับการล็อคก่อน C1 ยังคงปิดกั้น (พยายามรับการล็อกที่ X) C2 เสร็จสิ้นวิธีการและปลดล็อค ตอนนี้ C1 ได้รับล็อค เดาสิว่าโชคดีที่เรามีwhile
ลูปเพราะ C1 ทำการตรวจสอบลูป (การ์ด) และป้องกันไม่ให้ลบองค์ประกอบที่ไม่มีอยู่ออกจากบัฟเฟอร์ (C2 ได้รับแล้ว!) หากเราไม่มี a while
เราจะได้ค่าIndexArrayOutOfBoundsException
เป็น C1 พยายามลบองค์ประกอบแรกออกจากบัฟเฟอร์!
ตอนนี้
ตกลงตอนนี้ทำไมเราต้องแจ้งทั้งหมด
ในตัวอย่างผู้ผลิต / ผู้บริโภคด้านบนดูเหมือนว่าเราสามารถไปnotify
ได้ ดูเหมือนว่าด้วยวิธีนี้เพราะเราสามารถพิสูจน์ได้ว่าผู้คุมในห่วงการรอคอยสำหรับผู้ผลิตและผู้บริโภคนั้นไม่เหมือนกัน นั่นคือดูเหมือนว่าเราไม่สามารถมีเธรดที่กำลังรอในput
วิธีการและget
วิธีการได้เนื่องจากเพื่อให้เป็นจริงแล้วสิ่งต่อไปนี้จะต้องเป็นจริง:
buf.size() == 0 AND buf.size() == MAX_SIZE
(สมมติว่า MAX_SIZE ไม่ใช่ 0)
notifyAll
แต่นี้เป็นพอไม่ดีเราจำเป็นต้องใช้ มาดูกันว่าทำไม ...
สมมติว่าเรามีบัฟเฟอร์ขนาด 1 (เพื่อให้ง่ายต่อการปฏิบัติตามตัวอย่าง) ขั้นตอนต่อไปนี้นำเราไปสู่การหยุดชะงัก โปรดทราบว่าเมื่อใดก็ตามที่เธรดถูกปลุกด้วยการแจ้งเตือนสามารถเลือก JVM แบบไม่กำหนดค่าล่วงหน้าได้ - ซึ่งเป็นเธรดที่รอใด ๆ สามารถถูกปลุกได้ นอกจากนี้โปรดทราบว่าเมื่อหลายเธรดกำลังบล็อกการเข้าสู่วิธีการ (เช่นพยายามรับการล็อค) ลำดับของการได้มานั้นไม่สามารถกำหนดได้ โปรดจำไว้ว่าเธรดสามารถอยู่ในวิธีใดวิธีหนึ่งในแต่ละครั้งเท่านั้นวิธีการที่ซิงโครไนซ์อนุญาตให้เธรดเพียงหนึ่งเดียวเท่านั้นที่จะดำเนินการ หากลำดับเหตุการณ์ต่อไปนี้เกิดขึ้น - ผลลัพธ์การหยุดชะงัก:
ขั้นตอนที่ 1:
- P1 ใส่ 1 อักขระลงในบัฟเฟอร์
ขั้นตอนที่ 2:
- ความพยายาม P2 ครั้งput
- ตรวจสอบการรอลูป - ถ่านเรียบร้อยแล้วรอ
ขั้นตอนที่ 3:
- ความพยายาม P3 put
- ตรวจสอบการรอวน - ตัวอักษรรอแล้ว
ขั้นตอนที่ 4:
- C1 พยายามที่จะได้รับ 1 ถ่าน
- C2 พยายามที่จะได้รับ 1 ถ่าน - บล็อกในการเข้าสู่get
วิธีการ
- C3 พยายามที่จะได้รับ 1 ถ่าน - บล็อกเมื่อเข้าสู่get
วิธีการ
ขั้นตอนที่ 5:
- C1 กำลังดำเนินการget
เมธอด - รับตัวถ่าน, การเรียกnotify
, ออกจากเมธอด
- การnotify
ตื่นขึ้น P2
- แต่, C2 เข้าสู่วิธีการก่อนที่ P2 จะสามารถ (P2 ต้อง reacquire ล็อค) ดังนั้น P2 บล็อกเมื่อเข้าสู่put
วิธีการ
- C2 ตรวจสอบลูปรอไม่มีตัวอักษรในบัฟเฟอร์ดังนั้นรอ
- C3 เข้าสู่วิธีหลังจาก C2 แต่ก่อน P2 ตรวจสอบลูปรอไม่มีตัวอักษรในบัฟเฟอร์อีกต่อไปรอ
ขั้นตอนที่ 6:
- ตอนนี้: มี P3, C2 และ C3 รออยู่!
- ในที่สุด P2 ได้มาล็อคใส่ถ่านในบัฟเฟอร์โทรแจ้งเตือนออกจากวิธีการ
ขั้นตอนที่ 7:
- การแจ้งเตือนของ P2 ทำให้ P3 ตื่นขึ้นมา (โปรดจำไว้ว่าเธรดใด ๆ สามารถถูกปลุกได้)
- P3 ตรวจสอบเงื่อนไขการวนรอบการรอซึ่งมีอักขระอยู่ในบัฟเฟอร์อยู่แล้วดังนั้นรอ
- ไม่มีอีกต่อไปที่เรียกว่าข้อความและสามหัวข้อระงับอย่างถาวร!
การแก้ไข: แทนที่notify
ด้วยnotifyAll
ในรหัสผู้ผลิต / ผู้บริโภค (ด้านบน)