Java: แจ้งเตือน () เทียบกับ alertAll () อีกครั้ง


377

หากหนึ่ง Google สำหรับ "ความแตกต่างระหว่างnotify()และnotifyAll()" คำอธิบายมากมายจะปรากฏขึ้น (แยกย่อหน้า javadoc) ทั้งหมดนี้ทำให้จำนวนเธรดที่รอรับการปลุกเพิ่มขึ้นหนึ่งครั้งnotify()และnotifyAll()ต่อเนื่อง

อย่างไรก็ตาม (ถ้าฉันเข้าใจความแตกต่างระหว่างวิธีการเหล่านี้ถูกต้อง) มีเพียงหนึ่งเธรดเท่านั้นที่ถูกเลือกสำหรับการได้รับการตรวจสอบเพิ่มเติมเสมอ ในกรณีแรกรายการที่เลือกโดย VM ในกรณีที่สองรายการที่เลือกโดยตัวกำหนดเวลาระบบเธรด ขั้นตอนการเลือกที่แน่นอนสำหรับทั้งคู่ (ในกรณีทั่วไป) ไม่เป็นที่รู้จักสำหรับโปรแกรมเมอร์

สิ่งที่มีประโยชน์แตกต่างระหว่างการแจ้ง ()และnotifyAll ()แล้ว? ฉันพลาดอะไรไปรึเปล่า?


6
ไลบรารีที่มีประโยชน์ที่จะใช้สำหรับการทำงานพร้อมกันอยู่ในไลบรารีการทำงานพร้อมกัน ฉันขอแนะนำเหล่านี้เป็นตัวเลือกที่ดีกว่าในเกือบทุกกรณี ห้องสมุด Concurency ก่อนวันที่ Java 5.0 (ซึ่งเป็นสิ่งที่ถูกเพิ่มเข้ามาเป็นมาตรฐานในปี 2004)
Peter Lawrey

4
ฉันไม่เห็นด้วยกับปีเตอร์ ไลบรารีการทำงานพร้อมกันนั้นมีการใช้งานใน Java และมีโค้ด Java จำนวนมากที่ถูกเรียกใช้ทุกครั้งที่คุณเรียก lock (), unlock () ฯลฯ คุณสามารถถ่ายภาพตัวเองด้วยการใช้ไลบรารี่เก่าsynchronizedแต่ดี ค่อนข้างใช้งานยาก
Alexander Ryzhov

2
ความเข้าใจผิดที่สำคัญน่าจะเป็นเช่นนี้: ... มีเพียงหนึ่งเธรดที่ถูกเลือกเสมอสำหรับการได้มาซึ่งจอภาพเพิ่มเติม ในกรณีแรกรายการที่เลือกโดย VM ในกรณีที่สองรายการที่เลือกโดยตัวกำหนดเวลาระบบเธรด ความหมายที่เป็นอยู่นั้นเหมือนกัน ในขณะที่พฤติกรรมตามที่อธิบายไว้ถูกต้องสิ่งที่ขาดหายไปคือในnotifyAll()กรณี _the กระทู้อื่น ๆ หลังจากครั้งแรกที่ยังคงตื่นและจะได้รับจอภาพทีละคน ในnotifyกรณีที่ไม่มีกระทู้อื่น ๆ ที่จะปลุก ดังนั้นในทางปฏิบัติมันแตกต่างกันมาก!
BeeOnRope

1) ถ้ามีหลายเธรดกำลังรอวัตถุอยู่และการแจ้งเตือน () จะถูกเรียกเพียงครั้งเดียวบนวัตถุนั้น ยกเว้นหนึ่งในเธรดที่รอเธรดที่เหลือรอตลอดไป 2) ถ้าแจ้งเตือน () ใช้เพียงหนึ่งในเธรดที่รออยู่จำนวนมากเริ่มดำเนินการ หากแจ้งเตือนทั้งหมด () ใช้เธรดที่รออยู่ทั้งหมดจะได้รับแจ้ง แต่มีเพียงหนึ่งหัวข้อเท่านั้นที่เริ่มดำเนินการดังนั้นการใช้งาน Notificationall () ที่นี่คืออะไร
Chetan Gowda

@ChetanGowda การแจ้งหัวข้อทั้งหมด vs แจ้งให้ทราบเพียงหัวข้อเดียวเท่านั้นที่จริงมีความแตกต่างอย่างมีนัยสำคัญจนกว่าจะเห็นว่าบอบบาง แต่ความแตกต่างที่สำคัญเกิดขึ้นกับเราเมื่อคุณแจ้ง () เพียง 1 กระทู้กระทู้อื่น ๆ ทั้งหมดจะอยู่ในสถานะรอจนกว่าจะได้รับการแจ้งเตือนอย่างชัดเจน /สัญญาณ. การแจ้งทั้งหมดเธรดทั้งหมดจะถูกดำเนินการและดำเนินการให้เสร็จสิ้นในลำดับที่หนึ่งโดยไม่มีการแจ้งเตือนเพิ่มเติม - ที่นี่เราควรพูดว่าเธรดนั้นblockedไม่ใช่และwaitingเมื่อการประมวลผลblockedถูกระงับชั่วคราวจนกว่าเธรดอื่นจะอยู่ในsyncบล็อก
user104309

คำตอบ:


248

อย่างไรก็ตาม (ถ้าฉันเข้าใจความแตกต่างระหว่างวิธีการเหล่านี้ถูกต้อง) มีเพียงหนึ่งเธรดที่ถูกเลือกเสมอสำหรับการได้มาซึ่งการตรวจสอบเพิ่มเติม

นั่นไม่ถูกต้อง o.notifyAll()ตื่นทุกกระทู้ที่ถูกบล็อกในo.wait()การโทร กระทู้ที่ได้รับอนุญาตเท่านั้นที่จะกลับมาจากo.wait()หนึ่งโดยหนึ่ง แต่พวกเขาแต่ละคนจะได้รับการเปิดของพวกเขา


เพียงแค่ใส่มันขึ้นอยู่กับสาเหตุที่เธรดของคุณกำลังรอการแจ้งเตือน คุณต้องการบอกเธรดที่รออยู่ว่ามีบางอย่างเกิดขึ้นหรือคุณต้องการบอกเธรดทั้งหมดพร้อมกันหรือไม่

ในบางกรณีการรอเธรดทั้งหมดสามารถดำเนินการที่เป็นประโยชน์ได้เมื่อการรอเสร็จสิ้น ตัวอย่างจะเป็นชุดของเธรดที่กำลังรอให้ภารกิจบางอย่างเสร็จสิ้น เมื่องานเสร็จสิ้นเธรดที่รออยู่ทั้งหมดจะสามารถดำเนินธุรกิจต่อไปได้ ในกรณีเช่นนี้คุณจะต้องใช้alertAll ()เพื่อปลุกเธรดที่รออยู่ทั้งหมดพร้อมกัน

อีกกรณีหนึ่งตัวอย่างเช่นการล็อกแบบเอกสิทธิ์เฉพาะบุคคลร่วมกันมีเพียงหนึ่งเธรดที่รออยู่เท่านั้นที่สามารถทำสิ่งที่มีประโยชน์หลังจากได้รับแจ้ง (ในกรณีนี้รับการล็อก) ในกรณีเช่นนี้คุณค่อนข้างจะใช้แจ้ง () ดำเนินการอย่างเหมาะสมคุณสามารถใช้alertAll ()ในสถานการณ์นี้ได้เช่นกัน แต่คุณจะปลุกเธรดโดยไม่จำเป็นซึ่งไม่สามารถทำอะไรได้เลย


ในหลายกรณีรหัสที่จะรอเงื่อนไขจะถูกเขียนเป็นวนรอบ:

synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

ด้วยวิธีนี้ถ้ามีการo.notifyAll()เรียกใช้การรอมากกว่าหนึ่งเธรดและเธรดแรกที่ส่งคืนจากการo.wait()ทำให้ออกจากเงื่อนไขในสถานะที่ผิดพลาดเธรดอื่นที่ถูกปลุกจะกลับไปรออยู่


29
ถ้าคุณแจ้งเพียงหนึ่งเธรด แต่มีหลายอันกำลังรอวัตถุอยู่ VM จะกำหนดว่าจะแจ้งเตือนอันไหน
สัตว์สะเทินน้ำสะเทินบก

6
ฉันไม่สามารถพูดได้อย่างแน่นอนเกี่ยวกับข้อมูลจำเพาะ Java แต่โดยทั่วไปคุณควรหลีกเลี่ยงการตั้งสมมติฐานเกี่ยวกับรายละเอียดดังกล่าว ฉันคิดว่าคุณสามารถสันนิษฐานได้ว่า VM จะทำอย่างมีสติและเป็นธรรมอย่างยิ่ง
Liedman

15
Liedman ผิดอย่างรุนแรงข้อกำหนดของ Java ระบุอย่างชัดเจนว่าการแจ้งเตือน () ไม่รับประกันว่าจะยุติธรรม นั่นคือการเรียกเพื่อแจ้งเตือนแต่ละครั้งอาจทำให้เธรดเดียวกันกลับมาทำงานอีกครั้ง (คิวเธรดในจอภาพไม่เป็นไปอย่างยุติธรรมหรือไม่สมบูรณ์) อย่างไรก็ตามรับประกันว่ากำหนดการจะยุติธรรม นี่คือเหตุผลว่าทำไมในกรณีส่วนใหญ่ที่คุณมีเธรดมากกว่า 2 เธรดคุณควรเลือก alertAll
Yann TM

45
@YANTM ฉันทุกคนต่างก็มีความคิดสร้างสรรค์ แต่ฉันคิดว่าน้ำเสียงของคุณไม่ยุติธรรมเลย ฉันพูดอย่างชัดเจนว่า "ไม่สามารถพูดได้อย่างแน่นอน" และ "ฉันคิดว่า" ทำให้สบายขึ้นคุณเคยเขียนบางสิ่งเมื่อเจ็ดปีก่อนที่ไม่ถูกต้อง 100% หรือไม่?
Liedman

10
ปัญหาคือว่านี่เป็นคำตอบที่ยอมรับไม่ใช่คำถามของความภาคภูมิใจส่วนตัว หากคุณรู้ว่าคุณผิดตอนนี้โปรดแก้ไขคำตอบของคุณเพื่อพูดและชี้ไปที่เช่น xagyg pedagogic และคำตอบที่ถูกต้องด้านล่าง
Yann TM

330

เห็นได้ชัดว่า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ในรหัสผู้ผลิต / ผู้บริโภค (ด้านบน)


1
finnw - P3 ต้องตรวจสอบเงื่อนไขอีกครั้งเนื่องจากnotifyสาเหตุ P3 (เธรดที่เลือกในตัวอย่างนี้) เพื่อดำเนินการต่อจากจุดที่รอ (เช่นภายในwhileลูป) มีตัวอย่างอื่น ๆ ที่ไม่ทำให้เกิดการหยุดชะงักอย่างไรก็ตามในกรณีนี้การใช้งานnotifyไม่ได้รับประกันว่าจะไม่มีรหัสการหยุดชะงัก การใช้งานnotifyAllทำ
xagyg

4
@marcus ใกล้มาก ด้วย NotificationAll แต่ละเธรดจะเรียกล็อกอีกครั้ง (ทีละครั้ง) แต่โปรดทราบว่าหลังจากหนึ่งเธรดได้ทำการล็อกอีกครั้งและเรียกใช้งานเมธอด (และจากนั้นออก) ... เธรดถัดไปจะทำการล็อกอีกครั้งตรวจสอบ "ในขณะที่" และจะกลับไปที่ "รอ" (ขึ้นอยู่กับเงื่อนไขที่แน่นอน) ดังนั้นแจ้งให้ตื่นหนึ่งกระทู้ - ตามที่คุณระบุไว้อย่างถูกต้อง NotificationAll ปลุกเธรดทั้งหมดและแต่ละเธรดเรียกคืนการล็อกทีละครั้ง - ตรวจสอบเงื่อนไขของ "while" และเรียกใช้เมธอดหรือ "waits" อีกครั้ง
xagyg

1
@xagyg คุณกำลังพูดถึงสถานการณ์ที่โปรดิวเซอร์ทุกคนมีเพียงถ่านเดียวที่จะจัดเก็บหรือไม่? ถ้าเป็นเช่นนั้นตัวอย่างของคุณถูกต้อง แต่ไม่ใช่ IMO ที่น่าสนใจมาก ด้วยขั้นตอนเพิ่มเติมที่ฉันแนะนำคุณสามารถหยุดระบบเดียวกันได้ แต่ด้วยจำนวนอินพุตที่ไม่ จำกัด - ซึ่งเป็นรูปแบบที่มักใช้ในความเป็นจริง
eran

3
@codeObserver คุณถามว่า: "จะโทรไปแจ้ง AlertAll () ทำให้หลายเธรดที่รอคอยตรวจสอบเงื่อนไข while () ในเวลาเดียวกัน .. และดังนั้นจึงมีความเป็นไปได้ที่ก่อนหน้านี้ในขณะที่ saitsfied เธรด 2 เธรดจะก่อให้เกิด outOfBound ข้อยกเว้น? " ไม่เป็นไปไม่ได้เนื่องจากแม้ว่าจะมีหลายเธรดที่ต้องตื่น แต่ก็ไม่สามารถตรวจสอบเงื่อนไขในเวลาเดียวกันได้ พวกเขาแต่ละคนจะต้องได้รับการล็อคอีกครั้ง (ทันทีหลังจากรอ) ก่อนที่พวกเขาจะสามารถป้อนส่วนรหัสอีกครั้งและตรวจสอบอีกครั้งในขณะที่ ดังนั้นหนึ่งครั้ง
xagyg

4
@xagyg เป็นตัวอย่างที่ดี นี่ไม่ใช่หัวข้อจากคำถามเดิม; เพียงเพื่อการอภิปราย การหยุดชะงักเป็นปัญหาการออกแบบที่ไม่ถูกต้อง (แก้ไขให้ฉันถ้าฉันผิด) เพราะคุณมีการล็อคหนึ่งครั้งโดยทั้งสองใส่และรับ และ JVM ไม่ฉลาดพอที่จะโทรหาหลังจากได้รับการปลดล็อคและข้อรอง การล็อกที่ตายแล้วเกิดขึ้นเพราะทำให้ตัวเองตื่นขึ้นมาอีกตัวหนึ่งซึ่งทำให้ตัวเองกลับมารอ () เพราะในขณะที่ () จะทำให้สองคลาส (และสองล็อค) ทำงานได้หรือไม่ ดังนั้นใส่ {synchonized (get)}, รับ {(synchonized (put)}) กล่าวอีกนัยหนึ่ง get Will wake ใส่เท่านั้นและ put จะปลุกได้เท่านั้น
Jay

43

ความแตกต่างที่มีประโยชน์:

  • ใช้แจ้ง ()หากเธรดที่รออยู่ของคุณนั้นใช้แทนกันได้ (ลำดับที่พวกเขาตื่นขึ้นนั้นไม่สำคัญ) หรือหากคุณมีเธรดที่รอเพียงหนึ่งเธรด ตัวอย่างทั่วไปคือเธรดพูลที่ใช้เพื่อดำเนินการงานจากคิว - เมื่อมีการเพิ่มงานหนึ่งในเธรดจะได้รับการแจ้งเตือนให้ทำงานปลุกงานต่อไปและกลับไปที่โหมดสลีป

  • ใช้INFORMAll ()สำหรับกรณีอื่น ๆ ที่เธรดที่รออาจมีวัตถุประสงค์ที่แตกต่างกันและควรจะสามารถทำงานพร้อมกันได้ ตัวอย่างคือการดำเนินการบำรุงรักษาบนทรัพยากรที่ใช้ร่วมกันโดยที่หลายเธรดกำลังรอให้การดำเนินการเสร็จสิ้นก่อนที่จะเข้าถึงทรัพยากร


19

ฉันคิดว่ามันขึ้นอยู่กับวิธีการผลิตและบริโภคทรัพยากร หากมีวัตถุงาน 5 ชิ้นพร้อมใช้งานและคุณมีวัตถุผู้บริโภค 5 รายการมันจะเป็นการดีที่จะปลุกเธรดทั้งหมดโดยใช้ alertAll () เพื่อให้แต่ละคนสามารถประมวลผล 1 วัตถุงานได้

หากคุณมีวัตถุงานเพียงชิ้นเดียวจุดประสงค์ในการปลุกวัตถุผู้บริโภคทั้งหมดให้ตรงกันสำหรับวัตถุหนึ่งนั้นคืออะไร การตรวจสอบครั้งแรกครั้งแรกสำหรับงานที่พร้อมจะได้รับและกระทู้อื่น ๆ ทั้งหมดจะตรวจสอบและพบว่าพวกเขาไม่มีอะไรทำ

ผมพบว่าคำอธิบายที่ดีที่นี่ ในระยะสั้น:

โดยทั่วไปจะใช้วิธีการแจ้งเตือน () สำหรับกลุ่มทรัพยากรซึ่งมีจำนวน "ผู้บริโภค" หรือ "คนงาน" ที่ใช้ทรัพยากรโดยพลการ แต่เมื่อมีการเพิ่มทรัพยากรลงในกลุ่มผู้ใช้ที่รอคอยหรือผู้ปฏิบัติงานเพียงคนเดียวเท่านั้นที่สามารถจัดการได้ กับมัน จริง ๆ แล้วจะใช้เมธอดAllAll () ในกรณีอื่น ๆ ส่วนใหญ่ อย่างเคร่งครัดจะต้องแจ้งบริกรของเงื่อนไขที่อาจอนุญาตให้บริกรหลายคนดำเนินการต่อไป แต่มันก็ยากที่จะรู้ ดังนั้นตามกฎทั่วไปหากคุณไม่มีเหตุผลใด ๆ ในการใช้การแจ้งเตือน () คุณควรใช้การแจ้งเตือนทั้งหมด ()เพราะมันมักจะยากที่จะรู้ว่าสิ่งใดที่เธรดจะรอวัตถุใดวัตถุหนึ่งและสาเหตุ


11

โปรดทราบว่าด้วยยูทิลิตี้การทำงานพร้อมกันคุณยังมีตัวเลือกระหว่างsignal()และsignalAll()เนื่องจากวิธีการเหล่านี้เรียกว่า java.util.concurrentดังนั้นคำถามที่ยังคงอยู่แม้กระทั่งที่ถูกต้องด้วย

Doug Lea นำเสนอจุดที่น่าสนใจในหนังสือที่มีชื่อเสียงของเขา: ถ้าnotify()และThread.interrupt()เกิดขึ้นในเวลาเดียวกันการแจ้งอาจหายไป หากสิ่งนี้สามารถเกิดขึ้นได้และมีผลกระทบอย่างมากnotifyAll()เป็นตัวเลือกที่ปลอดภัยยิ่งขึ้นแม้ว่าคุณจะต้องจ่ายค่าโสหุ้ย (ตื่นหลายกระทู้มากเกินไป)


10

สรุปสั้น ๆ:

ชอบมากกว่าalertAll () ทั้งหมดมากกว่าalert ()เว้นแต่คุณจะมีแอปพลิเคชันแบบขนานขนาดใหญ่ที่มีเธรดจำนวนมากทำในสิ่งเดียวกัน

คำอธิบาย:

แจ้งเตือน () [... ] ตื่นกระทู้เดียว เนื่องจากการแจ้งเตือน ()ไม่อนุญาตให้คุณระบุเธรดที่ตื่นขึ้นจึงมีประโยชน์เฉพาะในแอปพลิเคชันแบบขนานขนาดใหญ่ - นั่นคือโปรแกรมที่มีเธรดจำนวนมากทั้งหมดจึงทำงานคล้ายกัน ในแอปพลิเคชั่นดังกล่าวคุณไม่สนใจว่ากระทู้ใดที่ถูกปลุกให้ตื่น

แหล่งที่มา: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

เปรียบเทียบการแจ้งเตือน ()กับalertAll ()ในสถานการณ์ที่อธิบายไว้ข้างต้น: แอปพลิเคชันแบบขนานขนาดใหญ่ที่เธรดกำลังทำสิ่งเดียวกัน หากคุณโทรแจ้งAlertAll ()ในกรณีนั้นNotificationAll ()จะทำให้เกิดการตื่นขึ้น (เช่นการตั้งเวลา) จำนวนเธรดจำนวนมากโดยส่วนใหญ่ไม่จำเป็น (เนื่องจากมีเพียงหนึ่งเธรดเท่านั้นที่สามารถดำเนินการต่อไปได้จริง) ตรวจสอบวัตถุรอ () , แจ้ง () , หรือAllAll ()ถูกเรียกเมื่อ) ดังนั้นจึงสูญเสียทรัพยากรการคำนวณ

ดังนั้นหากคุณไม่ได้มีโปรแกรมที่เป็นจำนวนมากของหัวข้อการทำสิ่งเดียวกันที่เห็นพ้องกันที่ต้องการnotifyAll ()มากกว่าแจ้ง () ทำไม? เนื่องจากตามที่ผู้ใช้รายอื่นได้ตอบไปแล้วในฟอรัมนี้ให้แจ้ง ()

ปลุกเธรดเดี่ยวที่รออยู่บนจอภาพของวัตถุนี้ [... ] ทางเลือกนั้นเป็นเรื่องที่ไม่แน่นอนและเกิดขึ้นตามดุลยพินิจของการดำเนินการ

แหล่งที่มา: Java SE8 API ( https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- )

ลองนึกภาพคุณมีแอปพลิเคชันผู้บริโภคสำหรับผู้ผลิตที่ผู้บริโภคพร้อม (เช่นรอ ()ไอเอ็นจี) เพื่อบริโภคผู้ผลิตพร้อม (เช่นรอ ()ไอเอ็นจี) เพื่อผลิตและคิวรายการ (ที่จะผลิต / บริโภค) ว่างเปล่า ในกรณีที่แจ้ง ()อาจจะตื่นขึ้นมาเพียงผู้บริโภคและไม่เคยผลิตเพราะทางเลือกที่มีการตื่นตัวขึ้นโดยพลการ วัฏจักรของผู้บริโภคผู้ผลิตจะไม่ทำให้คืบหน้าแม้ว่าผู้ผลิตและผู้บริโภคพร้อมที่จะผลิตและบริโภคตามลำดับ ผู้บริโภคตื่นขึ้นมา (เช่นออกจากสถานะรอ () ) ไม่นำรายการออกจากคิวเพราะว่างเปล่าและแจ้งให้ผู้ใช้รายอื่นดำเนินการต่อ ()

ในทางกลับกันAllAll ()ปลุกทั้งผู้ผลิตและผู้บริโภคให้ตื่นขึ้น ตัวเลือกที่กำหนดไว้นั้นขึ้นอยู่กับตัวกำหนดตารางเวลา แน่นอนขึ้นอยู่กับการใช้งานของตัวกำหนดตารางเวลาตัวจัดกำหนดการอาจเพียงกำหนดผู้บริโภค (เช่นถ้าคุณกำหนดเธรดผู้บริโภคที่มีลำดับความสำคัญสูงมาก) อย่างไรก็ตามสมมติฐานที่นี่เป็นที่ที่อันตรายของการจัดตารางเวลาการจัดตารางเวลาของผู้บริโภคเท่านั้นที่ต่ำกว่าอันตรายของ JVM เพียงตื่นขึ้นมาบริโภคเพราะใด ๆ จัดตารางเวลาการดำเนินการพอสมควรไม่ได้ทำให้เพียงโดยพลการตัดสินใจ แต่การใช้งานกำหนดการส่วนใหญ่พยายามอย่างน้อยเพื่อป้องกันความอดอยาก


9

นี่คือตัวอย่าง เรียกใช้ จากนั้นเปลี่ยนหนึ่งใน alertAll () เพื่อแจ้ง () และดูว่าเกิดอะไรขึ้น

คลาส ProducerConsumerExample

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

คลาส Dropbox

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

ระดับผู้บริโภค

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

คลาสผู้ผลิต

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}

8

จาก Joshua Bloch, Java Guru เองใน Effective Java 2nd edition:

"รายการ 69: ต้องการยูทิลิตี้การทำงานพร้อมกันให้รอและแจ้ง"


16
เหตุผลที่มีความสำคัญมากกว่าแหล่งที่มา
Pacerier

2
@ นายกรัฐมนตรีกล่าวว่าดี ฉันสนใจที่จะหาเหตุผลด้วยเช่นกัน เหตุผลหนึ่งที่เป็นไปได้คือการรอและการแจ้งเตือนในคลาสอ็อบเจ็กต์ขึ้นอยู่กับตัวแปรเงื่อนไขโดยนัย ดังนั้นในผู้ผลิตมาตรฐานและผู้บริโภคตัวอย่าง ..... ทั้งผู้ผลิตและผู้บริโภคจะรออยู่ในสภาพเดียวกันซึ่งอาจนำไปสู่การหยุดชะงักตามที่อธิบายโดย xagyg ในคำตอบของเขา ดังนั้นวิธีที่ดีกว่าคือการใช้ตัวแปรเงื่อนไข 2 แบบตามที่อธิบายไว้ในdocs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…
rahul

6

ฉันหวังว่านี้จะล้างข้อสงสัยบางอย่าง

แจ้งเตือน () : วิธีการแจ้งเตือน () ปลุกหนึ่งเธรดที่รอการล็อก (เธรดแรกที่เรียกว่าการรอ () บนล็อคนั้น

NotificationAll () : เมธอด informAll () จะปลุกเธรดทั้งหมดที่รอการล็อก JVM เลือกหนึ่งในเธรดจากรายการเธรดที่รอการล็อกและปลุกเธรดนั้นขึ้น

ในกรณีของเธรดเดี่ยวที่รอการล็อกจะไม่มีความแตกต่างอย่างมีนัยสำคัญระหว่างการแจ้งเตือน () และ INFORMAll () อย่างไรก็ตามเมื่อมีมากกว่าหนึ่งเธรดที่รอการล็อกทั้งแจ้งเตือน () และ alertAll () เธรดที่ถูกปลุกให้ตื่นขึ้นมานั้นอยู่ภายใต้การควบคุมของ JVMและคุณไม่สามารถควบคุมการปลุกเธรดเฉพาะทางโปรแกรมได้

เมื่อดูแวบแรกดูเหมือนว่าเป็นความคิดที่ดีที่จะเพียงแค่โทรแจ้งเตือน () เพื่อปลุกหนึ่งเธรด ดูเหมือนว่าไม่จำเป็นที่จะต้องปลุกกระทู้ทั้งหมด อย่างไรก็ตามปัญหาของการแจ้งเตือน () คือการที่เธรดตื่นขึ้นอาจไม่เหมาะสมที่จะปลุก (เธรดอาจรอเงื่อนไขอื่น ๆ หรือเงื่อนไขยังไม่พอใจสำหรับเธรดนั้น ฯลฯ ) ในกรณีดังกล่าวการแจ้งเตือน () อาจสูญหายและไม่มีเธรดอื่นใดที่จะปลุกซึ่งอาจนำไปสู่ประเภทการหยุดชะงัก (การแจ้งเตือนหายไปและเธรดอื่นทั้งหมดกำลังรอการแจ้งเตือน - ตลอดไป)

เพื่อหลีกเลี่ยงปัญหานี้จะเป็นการดีกว่าเสมอที่จะเรียกการแจ้งเตือนทั้งหมด () เมื่อมีเธรดมากกว่าหนึ่งเธรดรอการล็อก เมธอด INFORMAll () จะปลุกเธรดทั้งหมดดังนั้นจึงไม่มีประสิทธิภาพมาก อย่างไรก็ตามการสูญเสียประสิทธิภาพนี้ไม่สำคัญในแอปพลิเคชันโลกแห่งความจริง


6

มีสามสถานะสำหรับเธรด

  1. WAIT - เธรดไม่ได้ใช้ CPU รอบใด ๆ
  2. ถูกบล็อก - เธรดถูกบล็อกพยายามรับจอภาพมันอาจยังคงใช้รอบ CPU
  3. RUNNING - เธรดกำลังทำงาน

ตอนนี้เมื่อมีการแจ้งการแจ้งเตือน () JVM เลือกหนึ่งเธรดและย้ายไปยังสถานะ BLOCKED และด้วยเหตุนี้ไปยังสถานะ RUNNING เนื่องจากไม่มีการแข่งขันสำหรับวัตถุตรวจสอบ

เมื่อเรียก AlertAll () JVM เลือกเธรดทั้งหมดและย้ายทั้งหมดไปยังสถานะ BLOCKED กระทู้ทั้งหมดเหล่านี้จะได้รับการล็อคของวัตถุตามลำดับความสำคัญกระทู้ซึ่งสามารถที่จะได้รับจอภาพก่อนจะสามารถไปที่สถานะการทำงานก่อนและอื่น ๆ


คำอธิบายที่ยอดเยี่ยมเพียง
royatirek

5

ฉันประหลาดใจมากที่ไม่มีใครพูดถึงปัญหา "การสูญเสียการปลุก" ที่น่าอับอาย (google it)

โดยทั่วไป:

  1. หากคุณมีหลายกระทู้รออยู่ในสภาพเดียวกันและ
  2. หลายเธรดที่สามารถทำให้คุณเปลี่ยนจากสถานะ A ไปเป็นสถานะ B และ
  3. หลายเธรดที่สามารถทำให้คุณเปลี่ยนจากสถานะ B เป็นสถานะ A (โดยปกติจะเป็นเธรดเดียวกับใน 1) และ,
  4. การเปลี่ยนจากสถานะ A เป็น B ควรแจ้งเตือนเธรดใน 1

จากนั้นคุณควรใช้การแจ้งเตือนทั้งหมดเว้นแต่คุณจะมีการรับประกันที่พิสูจน์ได้ว่าการปลุกคืนที่หายไปเป็นไปไม่ได้

ตัวอย่างที่พบบ่อยคือคิว FIFO ที่เกิดขึ้นพร้อมกันโดยที่: enqueuers หลายตัว (1 และ 3 ข้างต้น) สามารถเปลี่ยนคิวของคุณจากที่ว่างเปล่าไปยังที่ไม่ว่างหลาย dequeuers (2. ด้านบน) สามารถรอเงื่อนไข "คิวไม่ว่างเปล่า" ว่างเปล่า -> ไม่ว่างเปล่าควรแจ้ง dequeuers

คุณสามารถเขียน interleaving ของการดำเนินการได้อย่างง่ายดายโดยเริ่มจากคิวที่ว่าง 2 enqueuers และ 2 dequeuers โต้ตอบและ 1 enqueuer จะยังคงหลับอยู่

นี่เป็นปัญหาที่สามารถเทียบเคียงได้กับปัญหาการหยุดชะงัก


ขอโทษ xagyg อธิบายในรายละเอียด ชื่อของปัญหาคือ "lost wakeup"
NickV

@Abhay Bansal: ฉันคิดว่าคุณขาดความจริงที่ว่า condition.wait () ปล่อยล็อคและได้รับการตอบกลับโดยเธรดที่ตื่นขึ้นมา
NickV

4

notify()จะปลุกหนึ่งเธรดในขณะที่notifyAll()จะปลุกทั้งหมด เท่าที่ฉันรู้ไม่มีพื้นกลาง แต่ถ้าคุณไม่แน่ใจว่าจะทำอย่างไรเพื่อให้กระทู้ของคุณใช้notify() notifyAll()ทำงานเหมือนจับใจทุกครั้ง


4

คำตอบทั้งหมดข้างต้นนั้นถูกต้องเท่าที่ฉันจะบอกได้ดังนั้นฉันจะบอกคุณอย่างอื่น สำหรับโค้ดการผลิตคุณควรใช้คลาสใน java.util.concurrent มีน้อยมากที่พวกเขาไม่สามารถทำเพื่อคุณในพื้นที่ของการเกิดพร้อมกันในจาวา


4

notify()notifyAll()ช่วยให้คุณสามารถเขียนโค้ดที่มีประสิทธิภาพมากกว่า

พิจารณาโค้ดต่อไปนี้ที่ถูกเรียกใช้จากหลายเธรดแบบขนาน:

synchronized(this) {
    while(busy) // a loop is necessary here
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notifyAll();
}

มันสามารถทำให้มีประสิทธิภาพมากขึ้นโดยใช้notify():

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notify();
}

ในกรณีที่ถ้าคุณมีจำนวนมากของหัวข้อหรือถ้าเงื่อนไขการรอคอยห่วงเป็นค่าใช้จ่ายในการประเมินผลจะเป็นอย่างเร็วกว่าnotify() notifyAll()ตัวอย่างเช่นหากคุณมี 1,000 เธรดแล้ว 999 เธรดจะถูกปลุกให้ตื่นและประเมินผลหลังจากครั้งแรกที่notifyAll()แล้วคือ 998, 997 และอื่น ๆ ในทางตรงกันข้ามกับการnotify()แก้ปัญหาเพียงหนึ่งกระทู้จะถูกปลุกให้ตื่นขึ้น

ใช้notifyAll()เมื่อคุณต้องการเลือกเธรดที่จะทำงานต่อไป:

synchronized(this) {
    while(idx != last+1)  // wait until it's my turn
        wait();
}
...
synchronized(this) {
    last = idx;
    notifyAll();
}

ในที่สุดสิ่งสำคัญคือต้องเข้าใจว่าในกรณีของnotifyAll()รหัสภายในsynchronizedบล็อกที่ถูกปลุกขึ้นมาจะถูกดำเนินการตามลำดับไม่ใช่ทั้งหมดในคราวเดียว notifyAll()สมมติว่ามีสามหัวข้อที่รออยู่ในตัวอย่างข้างต้นและโทรด้ายที่สี่ ทั้งสามเธรดจะถูกปลุกให้ตื่น แต่มีเพียงหนึ่งเธรดเท่านั้นที่จะเริ่มการทำงานและตรวจสอบสภาพของwhileลูป หากเงื่อนไขคือtrueมันจะเรียกwait()อีกครั้งและจากนั้นด้ายที่สองจะเริ่มดำเนินการและจะตรวจสอบwhileสภาพวงของมันและอื่น ๆ


4

นี่คือคำอธิบายที่ง่ายกว่า:

คุณถูกต้องว่าไม่ว่าคุณจะใช้ alert () หรือ alertAll () ผลลัพธ์ทันทีคือว่าอีกหนึ่งเธรดอื่นจะได้รับจอภาพและเริ่มดำเนินการ (สมมติว่ามีบางเธรดที่จริงถูกบล็อกเมื่อรอ () สำหรับวัตถุนี้เธรดที่ไม่เกี่ยวข้องอื่น ๆ จะไม่ดูดซับคอร์ที่มีอยู่ทั้งหมดเป็นต้น) ผลกระทบเกิดขึ้นในภายหลัง

สมมติว่าเธรด A, B และ C กำลังรอวัตถุนี้และเธรด A จะได้รับมอนิเตอร์ ความแตกต่างอยู่ในสิ่งที่เกิดขึ้นเมื่อ A ปล่อยจอภาพ หากคุณใช้แจ้งเตือน () แล้ว B และ C ยังคงถูกบล็อกอยู่ในรอ (): พวกเขาไม่ได้รอบนจอภาพพวกเขากำลังรอการแจ้งเตือน เมื่อ A ออกจอภาพ B และ C จะยังคงอยู่ที่นั่นรอการแจ้งเตือน ()

หากคุณใช้ INFORMAll () ดังนั้น B และ C จะมีสถานะ "รอการแจ้งเตือน" ขั้นสูงทั้งคู่และกำลังรอรับจอภาพ เมื่อ A ออกจอภาพทั้ง B หรือ C จะได้รับ (สมมติว่าไม่มีเธรดอื่นที่กำลังแข่งขันกับจอภาพนั้น) และเริ่มดำเนินการ


คำอธิบายที่ชัดเจนมาก ผลลัพธ์ของพฤติกรรมการแจ้งเตือนนี้สามารถนำไปสู่ ​​"สัญญาณที่ไม่ได้รับ" / "การแจ้งเตือนที่ไม่ได้รับ" ซึ่งนำไปสู่การหยุดชะงัก / ไม่มีสถานะความคืบหน้าของสถานะแอปพลิเคชัน P-Producer, C-Consumer P1, P2 และ C2 การโทรแบบ C1 แจ้งเตือน () และมีไว้สำหรับผู้ผลิต แต่ C2 สามารถปลุกขึ้นได้ดังนั้นทั้ง P1 และ P2 พลาดการแจ้งเตือนและจะรอการแจ้งเตือน "การแจ้งเตือน" (การแจ้งเตือน () อย่างชัดเจนต่อไป
user104309

4

คำตอบนี้เป็นเขียนกราฟิกและความเรียบง่ายของคำตอบที่ดีเยี่ยมโดยxagygรวมถึงความคิดเห็นโดยEran

เหตุใดจึงใช้ NotificationAll แม้ว่าผลิตภัณฑ์แต่ละรายการนั้นมีไว้สำหรับผู้บริโภครายเดียว

พิจารณาผู้ผลิตและผู้บริโภคง่ายขึ้นดังนี้

ผู้ผลิต:

while (!empty) {
   wait() // on full
}
put()
notify()

ผู้บริโภค:

while (empty) {
   wait() // on empty
}
take()
notify()

สมมติว่าผู้ผลิต 2 รายและผู้บริโภค 2 รายแบ่งปันขนาดบัฟเฟอร์ 1 ภาพต่อไปนี้แสดงสถานการณ์จำลองที่นำไปสู่การหยุดชะงักซึ่งจะหลีกเลี่ยงได้หากเธรดทั้งหมดใช้การแจ้งเตือนทั้งหมด

การแจ้งเตือนแต่ละครั้งจะมีป้ายกำกับพร้อมกับเธรดที่ถูกปลุกให้ตื่น

การหยุดชะงักเนื่องจากการแจ้งเตือน


3

ฉันต้องการพูดถึงสิ่งที่อธิบายใน Java Concurrency ในทางปฏิบัติ:

จุดแรกไม่ว่าจะแจ้งเตือนหรือแจ้งเตือนทั้งหมด?

It will be NotifyAll, and reason is that it will save from signall hijacking.

หากมีเธรด A และ B สองเธรดที่รออยู่บนเพรดิเคตเงื่อนไขที่แตกต่างกันของคิวเงื่อนไขเดียวกันและมีการเรียกการแจ้งเตือนมันจะเกิน JVM ที่เธรด JVM จะแจ้งเตือน

ตอนนี้ถ้าการแจ้งเตือนมีความหมายสำหรับเธรด A และ JVM ที่แจ้งเตือนเธรด B ดังนั้นเธรด B จะตื่นขึ้นและเห็นว่าการแจ้งเตือนนี้ไม่มีประโยชน์ดังนั้นจึงจะรออีกครั้ง และเธรด A จะไม่มีทางรู้เกี่ยวกับสัญญาณที่พลาดนี้

ดังนั้นการเรียกการแจ้งเตือนทั้งหมดจะแก้ไขปัญหานี้ได้ แต่จะมีผลกระทบต่อประสิทธิภาพเนื่องจากจะแจ้งเธรดทั้งหมดและเธรดทั้งหมดจะแข่งขันกันเพื่อล็อกเดียวกันและจะเกี่ยวข้องกับการสลับบริบทจึงโหลดซีพียู แต่เราควรใส่ใจเกี่ยวกับประสิทธิภาพการทำงานเฉพาะในกรณีที่มันทำงานอย่างถูกต้องหากพฤติกรรมของตัวเองไม่ถูกต้องประสิทธิภาพก็ไม่มีประโยชน์

ปัญหานี้สามารถแก้ไขได้ด้วยการใช้วัตถุเงื่อนไขของการล็อคการล็อคอย่างชัดเจนซึ่งระบุไว้ใน jdk 5 เนื่องจากให้การรอแต่ละสถานการณ์ที่แตกต่างกัน ที่นี่จะทำงานได้อย่างถูกต้องและจะไม่มีปัญหาด้านประสิทธิภาพเนื่องจากจะเรียกสัญญาณและตรวจสอบให้แน่ใจว่ามีเธรดเดียวเท่านั้นที่กำลังรอเงื่อนไขนั้น


3

notify()- เลือกเธรดสุ่มจากชุดรอของวัตถุและวางไว้ในBLOCKEDสถานะ เธรดที่เหลือในชุดรอของวัตถุยังคงอยู่ในWAITINGสถานะ

notifyAll()- ย้ายเธรดทั้งหมดจากชุดการรอของวัตถุเป็นBLOCKEDสถานะ หลังจากที่คุณใช้notifyAll()ไม่มีเธรดที่เหลืออยู่ในชุดรอของวัตถุที่ใช้ร่วมกันได้เนื่องจากทั้งหมดของพวกเขาอยู่ในBLOCKEDสถานะและไม่อยู่ในWAITINGสถานะ

BLOCKED- ถูกบล็อคสำหรับล็อคการได้มา WAITING- รอการแจ้งเตือน (หรือถูกปิดกั้นเพื่อการเข้าร่วมเสร็จสิ้น)


3

นำมาจากบล็อกบน Java ที่มีประสิทธิภาพ:

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

ดังนั้นสิ่งที่ฉันเข้าใจคือ (จากบล็อกดังกล่าวแสดงความคิดเห็นโดย "Yann TM" ในคำตอบที่ยอมรับและเอกสาร Java ):

  • แจ้งเตือน (): JVM ตื่นขึ้นหนึ่งกระทู้รอบนวัตถุนี้ การเลือกเธรดทำโดยพลการโดยไม่มีความยุติธรรม สามารถปลุกเธรดเดียวกันซ้ำแล้วซ้ำอีก ดังนั้นการเปลี่ยนแปลงสถานะของระบบ แต่ไม่มีการดำเนินการจริง ดังนั้นการสร้างlivelock
  • alertAll (): JVM ปลุกเธรดทั้งหมดแล้วเธรดทั้งหมดจะแข่งขันกันเพื่อล็อควัตถุนี้ ตอนนี้ตัวกำหนดตารางเวลาของ CPU เลือกเธรดที่ได้รับการล็อกบนวัตถุนี้ กระบวนการคัดเลือกนี้จะดีกว่าการคัดเลือกโดย JVM ดังนั้นจึงมั่นใจได้ถึงความมีชีวิตชีวา

2

ลองดูรหัสที่โพสต์โดย @xagyg

สมมติว่าสองหัวข้อที่แตกต่างกันกำลังรอสองเงื่อนไขที่แตกต่างกัน: หัวข้อแรกคือการรอคอยและด้ายที่สองคือการรอคอย
buf.size() != MAX_SIZEbuf.size() != 0

สมมติว่าในบางจุดbuf.size() ไม่เท่ากับ 0 JVM เรียกnotify()แทนnotifyAll()และเธรดแรกจะได้รับแจ้ง (ไม่ใช่อันที่สอง)

เธรดแรกตื่นขึ้นตรวจสอบbuf.size()ว่าอาจส่งคืนMAX_SIZEและกลับไปรอ เธรดที่สองไม่ได้ถูกปลุกขึ้นให้รอต่อไปและไม่เรียกget()ใช้


1

notify()ปลุกเธรดแรกที่เรียกใช้wait()บนวัตถุเดียวกัน

notifyAll()ปลุกเธรดทั้งหมดที่เรียกใช้wait()บนวัตถุเดียวกัน

เธรดลำดับความสำคัญสูงสุดจะทำงานก่อน


13
ในกรณีที่notify()มันไม่ตรง " เธรดแรก "
Bhesh Gurung

6
คุณไม่สามารถคาดเดาได้ว่า VM จะเลือกตัวเลือกใด พระเจ้าเท่านั้นที่รู้
Sergii Shevchyk

ไม่มีการรับประกันว่าใครจะเป็นคนแรก (ไม่มีความเป็นธรรม)
Ivan Voroshilin

มันจะปลุกเธรดแรกเท่านั้นหากระบบปฏิบัติการรับประกันได้และเป็นไปได้ว่าจะไม่เกิดขึ้น จริงๆแล้วมันขัดแย้งกับระบบปฏิบัติการ (และตัวกำหนดตารางเวลา) เพื่อกำหนดว่าจะให้เธรดใดตื่น
Paul Stelian

1

การแจ้งเตือนจะแจ้งเพียงหนึ่งเธรดที่อยู่ในสถานะรอขณะแจ้งเตือนทั้งหมดจะแจ้งให้เธรดทั้งหมดที่อยู่ในสถานะรอตอนนี้เธรดที่แจ้งเตือนทั้งหมดและเธรดที่ถูกบล็อกทั้งหมดมีสิทธิ์ใช้งานการล็อก อื่น ๆ ทั้งหมด (รวมถึงผู้ที่อยู่ในสถานะรอก่อนหน้านี้) จะอยู่ในสถานะถูกบล็อก


1

เพื่อสรุปรายละเอียดคำอธิบายที่ดีเยี่ยมด้านบนและในวิธีที่ง่ายที่สุดที่ฉันคิดได้นี่เป็นเพราะข้อ จำกัด ของจอมอนิเตอร์ในตัว JVM ซึ่ง 1) ได้มาจากยูนิตการซิงโครไนซ์ทั้งหมด (บล็อกหรือวัตถุ) และ 2) ไม่แยกแยะเกี่ยวกับเงื่อนไขเฉพาะที่กำลังรอ / แจ้งเตือนเมื่อ / ประมาณ

ซึ่งหมายความว่าหากมีหลายเธรดกำลังรอเงื่อนไขที่แตกต่างกันและใช้การแจ้งเตือน () เธรดที่เลือกอาจไม่เป็นเธรดที่จะทำให้เกิดความคืบหน้าเกี่ยวกับเงื่อนไขที่เพิ่งได้รับการตอบสนอง - ทำให้เธรดนั้น (และเธรดอื่น ๆ เพื่อตอบสนองเงื่อนไข ฯลฯ .) ไม่สามารถที่จะทำให้ความคืบหน้าและในที่สุดความอดอยากหรือ Hangup โปรแกรม

ในทางตรงกันข้าม INFORMAll () ทำให้เธรดที่รออยู่ทั้งหมดได้รับการล็อกอีกครั้งและตรวจสอบเงื่อนไขที่เกี่ยวข้องในที่สุด

ดังนั้นแจ้งเตือน () สามารถใช้ได้อย่างปลอดภัยเฉพาะเมื่อมีการรอเธรดการรับประกันใด ๆ ที่อนุญาตให้มีการดำเนินการหากมีการเลือกเธรดซึ่งโดยทั่วไปจะพึงพอใจเมื่อเธรดทั้งหมดภายในจอภาพเดียวกันตรวจสอบเพียงหนึ่งเดียวและเงื่อนไขเดียวกัน - ค่อนข้างหายาก กรณีในการใช้งานจริง


0

เมื่อคุณเรียกใช้การรอ () ของ "วัตถุ" (คาดว่าจะได้รับการล็อควัตถุ), ฝึกงานนี้จะปล่อยล็อคในวัตถุนั้นและช่วยให้เธรดอื่น ๆ ที่จะล็อคใน "วัตถุ" นี้ในสถานการณ์นี้จะมี มากกว่า 1 เธรดที่รอ "ทรัพยากร / วัตถุ" (พิจารณาเธรดอื่น ๆ ยังออกการรอบนวัตถุเดียวกันข้างต้นและลงไปทางที่จะมีเธรดที่เติมทรัพยากร / วัตถุและเรียกใช้แจ้งเตือน / แจ้งเตือนทั้งหมด)

ที่นี่เมื่อคุณออกการแจ้งเตือนของวัตถุเดียวกัน (จากด้านเดียวกัน / อื่น ๆ ของกระบวนการ / รหัส) สิ่งนี้จะปล่อยเธรดเดี่ยวที่ถูกบล็อกและรอ (ไม่ใช่เธรดที่รอทั้งหมด - เธรดที่เผยแพร่นี้จะถูกเลือกโดย JVM Thread ตัวกำหนดตารางเวลาและกระบวนการรับการล็อกทั้งหมดบนวัตถุนั้นเหมือนกับปกติ)

ถ้าคุณมีเธรดเดียวเท่านั้นที่จะแบ่งปัน / ทำงานกับวัตถุนี้มันก็โอเคที่จะใช้วิธีการแจ้งเตือน () เพียงอย่างเดียวในการดำเนินการรอการแจ้งเตือนของคุณ

หากคุณอยู่ในสถานการณ์ที่มีการอ่านมากกว่าหนึ่งเธรดและเขียนบนทรัพยากร / วัตถุตามตรรกะทางธุรกิจของคุณคุณควรไปที่ alertAll ()

ตอนนี้ฉันกำลังมองหาวิธีการที่ jvm ระบุและทำลายกระทู้รอเมื่อเราออกแจ้ง () บนวัตถุ ...


0

แม้ว่าจะมีคำตอบที่ชัดเจนอยู่บ้าง แต่ฉันก็ประหลาดใจด้วยจำนวนของความสับสนและความเข้าใจผิดที่ฉันได้อ่าน นี่อาจพิสูจน์แนวคิดที่ว่าควรใช้ java.util.concurrent ให้มากที่สุดแทนที่จะพยายามเขียนโค้ดที่เกิดขึ้นพร้อมกันที่เสียหาย กลับไปที่คำถาม: เพื่อสรุปแนวทางปฏิบัติที่ดีที่สุดในวันนี้คือการหลีกเลี่ยงการแจ้งเตือน () ในทุกสถานการณ์เนื่องจากปัญหาการปลุกที่หายไป ทุกคนที่ไม่เข้าใจสิ่งนี้ไม่ควรอนุญาตให้เขียนรหัสการทำงานพร้อมกันที่สำคัญของภารกิจ หากคุณกังวลเกี่ยวกับปัญหาการลงทุนในทิศทางเดียวกันวิธีที่ปลอดภัยวิธีหนึ่งที่จะทำให้การประกาศครั้งต่อไปคือ: 1. สร้างคิวการรอที่ชัดเจนสำหรับเธรดการรอ 2. ให้แต่ละเธรดในคิวรอก่อนหน้า 3. มีการเรียกเธรดทั้งหมดAll () เมื่อเสร็จสิ้น หรือคุณสามารถใช้ Java.util.concurrent. *,


จากประสบการณ์ของฉันบ่อยครั้งที่ใช้การรอ / แจ้งเตือนในกลไกของคิวที่เธรด ( Runnableการนำไปใช้) ประมวลผลเนื้อหาของคิว wait()ใช้แล้วเมื่อใดก็ตามที่คิวว่างเปล่า และnotify()ถูกเรียกเมื่อมีการเพิ่มข้อมูล -> ในกรณีเช่นนี้มีเพียง 1 เธรดที่เคยเรียกใช้wait()แล้วมันไม่ได้ดูงี่เง่าที่จะใช้notifyAll()ถ้าคุณรู้ว่ามีเพียง 1 เธรดที่รออยู่
bvdb

-2

ตื่นขึ้นมาทั้งหมดไม่ได้ทำให้ความสำคัญมากที่นี่ รอการแจ้งเตือนและแจ้งเตือนทั้งหมดเหล่านี้จะถูกวางหลังจากการเป็นเจ้าของจอภาพของวัตถุ หากเธรดอยู่ในช่วงรอและมีการเรียกการแจ้งเตือนเธรดนี้จะใช้การล็อกและไม่มีเธรดอื่นที่จุดนั้นสามารถใช้การล็อกนั้นได้ ดังนั้นการเข้าถึงพร้อมกันจึงไม่สามารถเกิดขึ้นได้เลย เท่าที่ฉันรู้ว่าสายใด ๆ ที่จะรอการแจ้งเตือนและแจ้งเตือนทั้งหมดสามารถทำได้เฉพาะหลังจากการล็อควัตถุ ถูกต้องฉันถ้าฉันผิด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.