รูปแบบผู้ผลิตของผู้ผลิตเป็นรูปแบบที่พบบ่อยที่สุดในหมู่มัลติเธรด: ด้ายผู้ผลิต (หนึ่งหรือหนึ่ง) สร้างขนมปังและใส่ลงในตะกร้า (ชุดหรืออาร์เรย์) และในเวลาเดียวกันด้ายผู้บริโภค (หนึ่งหรือมากกว่า) นำขนมปังออกจากตะกร้า (ชุดหรืออาร์เรย์) แม้ว่าพวกเขาจะมีงานที่แตกต่างกัน แต่ทรัพยากรที่พวกเขาดำเนินการเหมือนกันซึ่งสะท้อนถึงวิธีการสื่อสารระหว่างเธรด
บทความนี้จะอธิบายสถานการณ์ของผู้ผลิตรายเดียวและผู้บริโภครายเดียวก่อนจากนั้นอธิบายสถานการณ์ของรุ่นผู้ผลิตและผู้บริโภคหลายรุ่น สองโหมดนี้จะถูกนำไปใช้โดยใช้กลไกการรอ ()/nofity ()/nofityall () และกลไกการล็อค ()/ปลดล็อค () ตามลำดับ
ก่อนที่จะเริ่มต้นการแนะนำรูปแบบให้อธิบายรายละเอียดการใช้งานของวิธีการรอ (), แจ้ง () และ notifyall () วิธีการรวมถึงการใช้งานที่ดีขึ้นของการล็อค ()/ปลดล็อค () รอ ()/สัญญาณ ()/signalall ()
1. หลักการของการรอคอยและปลุกกลไก
รอ (), แจ้ง () และแจ้งเตือน () ตามลำดับแสดงถึงเธรดที่เข้าสู่การนอนหลับตื่นขึ้นมาด้ายนอนหลับและตื่นขึ้นมาด้ายนอนหลับทั้งหมด แต่วัตถุคือเธรดใด นอกจากนี้ทั้งสามวิธีที่อธิบายไว้ในเอกสาร API จะต้องใช้ภายใต้หลักฐานของจอภาพที่ถูกต้อง (ซึ่งสามารถเข้าใจได้ว่าเป็นล็อค) วิธีการทั้งสามนี้เกี่ยวข้องกับการล็อคอย่างไร?
การใช้บล็อกการซิงโครไนซ์บล็อกซิงโครไนซ์ (OBJ) {} หรือฟังก์ชั่นการซิงโครไนซ์เป็นตัวอย่าง, รอ (), แจ้งเตือน () และแจ้งเตือน () สามารถใช้ในโครงสร้างรหัสของพวกเขาเพราะพวกเขาทั้งหมดถือล็อค
สำหรับบล็อกรหัสการซิงโครไนซ์สองตัวต่อไปนี้ล็อค OBJ1 และล็อค OBJ2 จะถูกใช้ตามลำดับ เธรด 1 และเธรด 2 เรียกใช้รหัสการซิงโครไนซ์ที่สอดคล้องกับ OBJ1 และเธรด 3 และเธรด 4 เรียกใช้รหัสการซิงโครไนซ์ที่สอดคล้องกับ OBJ2
คลาส MyLock ใช้งาน Runnable {Public Int Flag = 0; วัตถุ obj1 = วัตถุใหม่ (); วัตถุ obj2 = วัตถุใหม่ (); Public Void Run () {ในขณะที่ (จริง) {if (flag%2 = 0) {ซิงโครไนซ์ (obj1) {// threads t1 และ t2 ดำเนินงานการซิงโครไนซ์นี้ // ลอง {obj1.wait ();} catch (interruptedexception i) ซิงโครไนซ์ (obj2) {// เธรด t3 และ t4 ดำเนินการซิงโครไนซ์งานนี้ // ลอง {obj2.wait ();} catch (interruptedexception i) {} //obj2.notify () //obj2.notifyall ()}}}}}}}}}}} ml = new mylock (); เธรด t1 = เธรดใหม่ (ml); เธรด t2 = เธรดใหม่ (ml); เธรด t3 = เธรดใหม่ (ml); เธรด t4 = เธรดใหม่ (ml); t1.start (); t2.start (); ลอง {thread.sleep (1)} catch (interruptedException i) {}; Ml.Flag ++; t3.start (); t4.start (); -เมื่อ T1 เริ่มดำเนินการเพื่อรอ () มันจะเข้าสู่สถานะการนอนหลับ แต่มันไม่ใช่การนอนหลับปกติ แต่นอนในพูลเธรดที่ระบุโดย OBJ1 (จริง ๆ แล้วจอภาพสอดคล้องกับพูลเธรด แต่จอภาพและล็อคถูกผูกไว้ด้วยกันในเวลานี้) เมื่อ T2 เริ่มดำเนินการพบว่าล็อค OBJ1 ถูกจัดขึ้นโดยเธรดอื่น ๆ และจะเข้าสู่สถานะการนอนหลับ คราวนี้เป็นเพราะทรัพยากรล็อคกำลังรอมากกว่าการนอนหลับที่ป้อนโดยรอ () เนื่องจาก T2 ได้พิจารณาแล้วว่ามันใช้สำหรับการล็อค OBJ1 มันจะเข้าสู่การนอนหลับพูลเธรด OBJ1 แทนที่จะนอนหลับธรรมดา ในทำนองเดียวกัน T3 และ T4 เธรดทั้งสองนี้จะเข้าสู่พูลเธรด OBJ2 เพื่อนอนหลับ
เมื่อเธรดดำเนินการเพื่อแจ้ง () การแจ้งเตือนนี้ () จะสุ่มปลุกเธรดใด ๆ ในพูลเธรดที่สอดคล้องกับการล็อค ตัวอย่างเช่น obj1.notify () จะตื่นขึ้นมาด้ายการนอนหลับใด ๆ ในพูลเธรด OBJ1 (แน่นอนถ้าไม่มีด้ายนอนหลับแล้วทำอะไรเลย) ในทำนองเดียวกัน NotifyAll () จะปลุกเธรดการนอนหลับทั้งหมดในพูลเธรดที่สอดคล้องกันของล็อค
สิ่งที่คุณต้องคิดคือ "ล็อคที่สอดคล้องกัน" เนื่องจากการล็อคจะต้องระบุอย่างชัดเจนเมื่อโทรรอ (), แจ้ง () และแจ้งเตือน () ตัวอย่างเช่น obj1.wait () หากการล็อคเป็นของมันถูกละเว้นมันหมายถึงวัตถุนี้นั่นคือคำนำหน้าของทั้งสามวิธีนี้สามารถละเว้นได้ในฟังก์ชั่นการซิงโครไนซ์แบบไม่คงที่
ในระยะสั้นเมื่อใช้การซิงโครไนซ์ใช้ล็อคและเธรดมีบ้านและพื้นฐานทั้งหมดจะถูกกำหนดโดยการล็อคเป็นของ ตัวอย่างเช่นเมื่อการซิงโครไนซ์เธรดจะกำหนดว่าการล็อคนั้นไม่ได้ใช้งานเพื่อตัดสินใจว่าจะเรียกใช้รหัสที่ตามมาและกำหนดว่าจะไปที่พูลเธรดเฉพาะเพื่อนอนหลับหรือไม่ เมื่อตื่นขึ้นมามันจะปลุกเธรดในพูลเธรดที่สอดคล้องกับล็อคเท่านั้น
ในการประยุกต์ใช้วิธีการเหล่านี้โดยทั่วไปในงานรอ () และแจ้งเตือน ()/notifyall () ปรากฏเป็นคู่และดำเนินการทีละหนึ่ง กล่าวอีกนัยหนึ่งในระหว่างการดำเนินการแบบซิงโครนัสอะตอมนี้ไม่ว่าจะเป็นการรอ () จะถูกดำเนินการเพื่อนอนหลับหรือแจ้งให้ทราบ () จะถูกดำเนินการเพื่อปลุกเธรดการนอนหลับในพูลเธรด เพื่อให้บรรลุการดำเนินการแบบเลือกคุณสามารถพิจารณาใช้การทำเครื่องหมายเป็นพื้นฐานสำหรับการตัดสิน อ้างถึงตัวอย่างต่อไปนี้
2. ล็อคและเงื่อนไข
สามวิธีของซีรีย์ WAIT () มี จำกัด มากเนื่องจากทั้งการนอนหลับและตื่นขึ้นมาพร้อมกับการล็อคอย่างสมบูรณ์ ตัวอย่างเช่นเธรดที่เกี่ยวข้องกับการล็อค OBJ1 สามารถปลุกเธรดในพูลเธรด OBJ1 ได้ แต่ไม่สามารถปลุกเธรดที่เกี่ยวข้องกับล็อค OBJ2; ตัวอย่างเช่นเมื่อซิงโครไนซ์ซิงโครไนซ์ซิงโครไนซ์เดิมถูกซิงโครไนซ์การล็อคจะได้รับโดยปริยายโดยอัตโนมัติเมื่อการซิงโครไนซ์เริ่มต้นขึ้นและหลังจากงานทั้งหมดถูกดำเนินการมันจะปล่อยล็อคโดยอัตโนมัติซึ่งหมายความว่าการกระทำของการล็อคและการปล่อยล็อคไม่สามารถควบคุมได้ด้วยตนเอง
เริ่มต้นจาก JDK 1.5, Java จัดเตรียมแพ็คเกจ java.util.concurrent.locks ซึ่งให้อินเทอร์เฟซล็อคอินเตอร์เฟสเงื่อนไขและอินเตอร์เฟส ReadWriteLock อินเทอร์เฟซสองตัวแรกจะแยกแยะวิธีการล็อคและการตรวจสอบ (การนอนหลับการทำงานปลุก) อินเทอร์เฟซล็อคมีเพียงล็อคเท่านั้น ด้วยวิธีการล็อค newconditon () สามารถสร้างจอภาพหนึ่งจอขึ้นไปที่เกี่ยวข้องกับล็อคได้ จอมอนิเตอร์แต่ละตัวมีวิธีการนอนหลับและการปลุกของตัวเอง กล่าวอีกนัยหนึ่งล็อคแทนที่การใช้วิธีการซิงโครไนซ์และบล็อกรหัสที่ซิงโครไนซ์และเงื่อนไขแทนที่การใช้วิธีการตรวจสอบวัตถุ
ดังที่แสดงในรูปด้านล่าง:
เมื่อเธรดดำเนินการเงื่อนไข 1.await () เธรดจะเข้าสู่พูลเธรดที่สอดคล้องกับการตรวจสอบเงื่อนไข 1 ถึงการนอนหลับ เมื่อมีการดำเนินการเงื่อนไข 1.Signal () เธรดใด ๆ ในพูลเงื่อนไข 1 จะถูกปลุกแบบสุ่ม เมื่อมีการดำเนินการเงื่อนไข 1.signalall () เธรดทั้งหมดในสระว่ายน้ำเงื่อนไข 1 จะถูกปลุก ในทำนองเดียวกันก็เป็นจริงสำหรับการตรวจสอบเงื่อนไข 2
แม้ว่าจะมีจอภาพหลายจอตราบใดที่พวกเขาเชื่อมโยงกับวัตถุล็อคเดียวกันเธรดอื่น ๆ ก็สามารถทำงานได้ทั่วจอมอนิเตอร์ ตัวอย่างเช่นเธรดในเงื่อนไข 1 สามารถเรียกใช้เงื่อนไข 2.signal () เพื่อปลุกเธรดในพูลเงื่อนไข 2
หากต้องการใช้วิธีการเชื่อมโยงการล็อคและจอภาพให้ดูขั้นตอนต่อไปนี้:
นำเข้า java.util.concurrent.locks.*; ล็อค l = ใหม่ reentrantlock (); เงื่อนไข con1 = l.newcondition (); เงื่อนไข con2 = l.newcondition (); l.lock (); ลอง {// ส่วนโค้ด // เนื่องจากเซ็กเมนต์รหัสอาจผิดปกติปลดล็อค () ต้องดำเนินการให้ลองใช้และปลดล็อค () จะต้องใส่ลงในส่วนสุดท้าย}สำหรับการใช้งานเฉพาะโปรดดูรหัสตัวอย่างสำหรับการล็อคและเงื่อนไขในภายหลัง
3. ผู้ผลิตรายเดียวรุ่นผู้บริโภคเดี่ยว
เธรดผู้ผลิตเธรดผู้บริโภค สำหรับแต่ละขนมปังที่ผลิตโดยผู้ผลิตวางไว้บนจานผู้บริโภคจะนำขนมปังออกจากจานเพื่อการบริโภค พื้นฐานสำหรับผู้ผลิตที่จะตัดสินว่าจะดำเนินการผลิตต่อไปคือไม่มีขนมปังอยู่บนจานในขณะที่พื้นฐานสำหรับผู้บริโภคที่จะตัดสินว่าจะกินหรือไม่คือมีขนมปังอยู่บนจาน เนื่องจากในโหมดนี้มีเพียงขนมปังเพียงก้อนเดียวเท่านั้นที่วางอยู่บนจานแผ่นสามารถละเว้นได้และผู้ผลิตและผู้บริโภคสามารถส่งมอบขนมปังทีละขั้นตอน
อันดับแรกเราต้องอธิบายทั้งสามหมวดหมู่นี้: หนึ่งคือทรัพยากรที่ดำเนินการโดยหลายเธรด (นี่คือขนมปัง) ที่สองคือผู้ผลิตและที่สามคือผู้บริโภค ในตัวอย่างต่อไปนี้ฉันห่อหุ้มวิธีการผลิตขนมปังและบริโภคขนมปังเข้าไปในชั้นเรียนผู้ผลิตและผู้บริโภคตามลำดับซึ่งง่ายกว่าที่จะเข้าใจว่าพวกเขาถูกห่อหุ้มในชั้นเรียนขนมปัง
// คำอธิบายทรัพยากร: ชื่อและจำนวนขนมปังกำหนดโดยจำนวนขนมปังขนมปัง {ชื่อสตริงสาธารณะ; จำนวน int สาธารณะ = 1; ธงบูลีนสาธารณะ = เท็จ; // เครื่องหมายนี้ให้เครื่องหมายการตัดสินสำหรับการรอ () และแจ้งเตือน ()} // ทรัพยากรขนมปังที่ประมวลผลโดยผู้ผลิตและผู้บริโภคเหมือนกัน เพื่อให้แน่ใจว่าสิ่งนี้ // คลาสขนมปังสามารถออกแบบตามรูปแบบ Singleton หรือวัตถุขนมปังเดียวกันสามารถส่งผ่านไปยังผู้ผลิตและผู้บริโภคผ่านวิธีการก่อสร้าง ใช้วิธีหลังที่นี่ // อธิบายผู้ผลิตคลาสผู้ผลิตใช้งาน {ขนมปังส่วนตัว B; // สมาชิกของผู้ผลิต: ทรัพยากรที่ต้องการประมวลผลผู้ผลิต (ขนมปัง b) {this.b = b; } // การจัดหาวิธีการผลิตโมฆะสาธารณะ BREAD PREADE PROUSEL (ชื่อสตริง) {B.NAME = ชื่อ + B.Count; B.Count ++; } public void run () {ในขณะที่ (จริง) {ซิงโครไนซ์ (bread.class) {// ใช้ bread.class เป็นตัวระบุล็อคเพื่อให้บล็อกรหัสซิงโครไนซ์ของผู้ผลิตและผู้บริโภคสามารถใช้ล็อคเดียวกันได้ ลอง {bread.class.wait ();} catch (interruptedException i) {}} ผลิต ("ขนมปัง"); System.out.println (thread.currentthread (). getName ()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------ - // notify () จะต้องซิงโครไนซ์มิฉะนั้นล็อคได้รับการปล่อยตัวและการกระทำที่ปลุกไม่สามารถดำเนินการได้ // ps: ในงานการซิงโครไนซ์รอ () และแจ้งเตือน () ควรดำเนินการมิฉะนั้นกระทู้ของฝ่ายอื่นจะสับสน}}}} // = b;} // วิธีการให้การบริโภคการบริโภคสตริงสาธารณะ () {return b.name;} public void run () {ในขณะที่ (จริง) {synchronized (bread.class) {ถ้า (! System.out.println (thread.currentthread (). getName ()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - // 2.ควรผลิตและบริโภคผลการดำเนินการขั้นสุดท้ายและนี่เป็นวัฏจักรต่อเนื่อง ดังนี้:
Thread-0 --- โปรดิวเซอร์ ---- Bread1Thread-1 --- ผู้บริโภค ------- Bread1Thread-0 --- โปรดิวเซอร์ ---- Bread2Thread-1 -------------------------------------------------------------------------------------------------------------------------------------- -
4. ใช้ล็อคและเงื่อนไขเพื่อตระหนักถึงรูปแบบการผลิตและการบริโภคเดี่ยว
รหัสมีดังนี้:
นำเข้า java.util.concurrent.locks.*; ขนมปังคลาส {ชื่อสตริงสาธารณะ; จำนวน int สาธารณะ = 1; ธงบูลีนสาธารณะ = เท็จ; // จัดเตรียมวัตถุล็อคเดียวกันและวัตถุเงื่อนไขเดียวกันสำหรับผู้ผลิตและผู้บริโภคล็อคล็อคแบบคงที่สาธารณะ = ใหม่ reentrantlock (); เงื่อนไขคงที่สาธารณะ = lock.newCondition ();} ผู้ผลิตในชั้นเรียนใช้การใช้งานได้ {ขนมปังส่วนตัว B; ผู้ผลิต (ขนมปัง b) {this.b = b; } โมฆะสาธารณะผลิต (ชื่อสตริง) {b.name = ชื่อ + b.count; B.Count ++; } โมฆะสาธารณะเรียกใช้ () {ในขณะที่ (จริง) {// ใช้ bread.lock เพื่อล็อคทรัพยากร Bread.lock.lock (); ลอง {ถ้า (b.flag) {ลอง {bread.condition.await ();} catch (interruptedException i) {}} ผลิต ("ขนมปัง"); System.out.println (thread.currentthread (). getName ()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Bread.condition.signal ();} ในที่สุด {bread.lock.unlock (); ลอง {ถ้า (! b.flag) {ลอง {bread.condition.await ();} catch (interruptedException i) {}} System.out.println (thread.currentthread (). getName ()+"-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - // 2.5. รูปแบบการผลิตและการบริโภคหลายครั้ง (ขนมปังเดี่ยว)
ที่นี่ก่อนอื่นเราจะอธิบายรูปแบบของผู้ผลิตหลายรายและผู้บริโภคหลายราย แต่ส่วนใหญ่หนึ่งขนมปังในเวลาเดียวกัน โมเดลนี้อาจไม่เหมาะในความเป็นจริง แต่เพื่อนำไปสู่การผลิตหลายครั้งจริงและรูปแบบการบริโภคหลายครั้งในภายหลังฉันคิดว่าจำเป็นต้องอธิบายโมเดลนี้ที่นี่และวิเคราะห์โมเดลนี้และวิธีการพัฒนาจากการผลิตครั้งเดียวและรหัสการบริโภคเดียว
ดังที่แสดงในรูปด้านล่าง:
จากการผลิตครั้งเดียวและการบริโภคครั้งเดียวไปจนถึงการผลิตหลายครั้งและการบริโภคหลายครั้งเนื่องจากปัญหาด้านความปลอดภัยแบบมัลติเธรดและปัญหาการหยุดชะงักมีสองประเด็นที่ต้องพิจารณา:
สำหรับฝ่ายใดฝ่ายหนึ่งมัลติเธรดสามารถบรรลุความสามารถในการผลิตหรือการบริโภคเช่นเดียวกับเธรดเดี่ยวได้อย่างไร กล่าวอีกนัยหนึ่งวิธีการทำให้มัลติเธรดดูเป็นเธรดเดี่ยว ความแตกต่างที่ใหญ่ที่สุดระหว่างมัลติเธรดและเธรดเดี่ยวคือปัญหาด้านความปลอดภัยแบบมัลติเธรด ดังนั้นตราบใดที่คุณมั่นใจว่างานที่ดำเนินการโดยมัลติเธรดสามารถซิงโครไนซ์ได้
คำถามแรกพิจารณาถึงปัญหาของการทำมัลติเธรดในฝ่ายหนึ่งและคำถามที่สองพิจารณาว่าทั้งสองฝ่ายสามารถร่วมมือกันอย่างกลมกลืนกับการผลิตและการบริโภคให้เสร็จสมบูรณ์ นั่นคือวิธีการตรวจสอบให้แน่ใจว่าด้านหนึ่งของผู้ผลิตและผู้บริโภคกำลังนอนหลับในขณะที่อีกด้านหนึ่งทำงานอยู่ เพียงแค่ตื่นขึ้นมาอีกฝ่ายเมื่อฝ่ายหนึ่งได้ทำภารกิจการซิงโครไนซ์เสร็จแล้ว
ในความเป็นจริงจากเธรดเดี่ยวไปจนถึงมัลติเธรดมีสองประเด็นที่ต้องพิจารณา: การไม่ซิงโครไนซ์และการหยุดชะงัก (1) เมื่อทั้งผู้ผลิตและด้านผู้บริโภคมีหลายเธรดการทำมัลติเธรดของผู้ผลิตสามารถถือได้ว่าเป็นด้ายโดยรวมและหลายเธรดของด้านผู้บริโภคโดยรวมซึ่งแก้ปัญหาความปลอดภัยของด้าย (2) การรวมการผลิตทั้งหมดและผู้บริโภคทั้งหมดถือเป็นมัลติเธรดเพื่อแก้ปัญหาการหยุดชะงัก วิธีการแก้ปัญหาการหยุดชะงักในชวาคือการตื่นขึ้นมาอีกฝ่ายหรือตื่นขึ้นมาทุกอย่าง
คำถามคือวิธีการให้แน่ใจว่าการซิงโครไนซ์ระหว่างหลายเธรดของบุคคลบางฝ่าย? รหัสของผู้บริโภครายเดียวถูกวิเคราะห์โดยการดำเนินการแบบหลายเธรดเป็นตัวอย่าง
ในขณะที่ (จริง) {ซิงโครไนซ์ (bread.class) {ถ้า (! b.flag) {ลอง {bread.class.wait ();} catch (interruptedException i) {}} System.out.println (thread.currentthread (). getName ()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -สมมติว่าเธรดการบริโภค 1 ตื่นขึ้นมากระทู้การบริโภค 2 หลังจากบริโภคขนมปังก้อนหนึ่งและยังคงวนรอบตัดสินว่า (! ธง) มันจะรอและล็อคจะถูกปล่อยออกมา สมมติว่า CPU เพียงแค่เลือกเธรดผู้บริโภค 2 เธรดผู้บริโภค 2 จะเข้าสู่การรอ เมื่อผู้ผลิตผลิตขนมปังก้อนหนึ่งสมมติว่าการบริโภคด้าย 1 ถูกปลุกขึ้นมามันจะยังคงบริโภคขนมปังที่ผลิตใหม่จากคำสั่งรอสมมติว่าการบริโภคด้าย 2 ถูกปลุกอีกครั้ง เมื่อ CPU การบริโภคการบริโภค 2 ถูกเลือกโดย CPU การบริโภคเธรด 2 จะกินลงจากคำสั่งรอและขนมปังที่เพิ่งผลิตจะถูกบริโภค ปัญหาเกิดขึ้นอีกครั้ง การบริโภคที่ตื่นอย่างต่อเนื่องเธรด 1 และ 2 กินขนมปังเดียวกันซึ่งหมายความว่าขนมปังจะบริโภคซ้ำ ๆ นี่เป็นปัญหาการซิงค์แบบมัลติเธรดอีกครั้ง
หลังจากพูดถึงมันเป็นเวลานานจริง ๆ แล้วมันง่ายมากที่จะวิเคราะห์หลังจากขยายขอบเขตการมองเห็น ตราบใดที่สองหรือมากกว่านั้นของฝ่ายหนึ่งรอการตัดสิน b.flag จากนั้นสองหรือมากกว่านั้นอาจถูกปลุกขึ้นมาอย่างต่อเนื่องและยังคงผลิตหรือบริโภคลง สิ่งนี้สร้างปัญหาของการซิงโครไนซ์แบบมัลติเธรด
ปัญหาของความไม่มั่นคงอยู่ในความจริงที่ว่าหลายเธรดในฝ่ายเดียวกันยังคงผลิตหรือบริโภคลงหลังจากตื่นอย่างต่อเนื่อง สิ่งนี้เกิดจากคำสั่ง IF หากเธรดรอสามารถหันกลับเพื่อตรวจสอบว่า b.flag เป็นจริงหรือไม่หลังจากการปลุกมันสามารถตัดสินใจได้ว่าจะรอต่อไปหรือการผลิตหรือการบริโภคลง
คุณสามารถแทนที่คำสั่ง IF ด้วยคำสั่งที่เป็นไปตามข้อกำหนด ด้วยวิธีนี้ไม่ว่าจะมีหลายเธรดในบางฝ่ายที่ถูกปลุกให้ตื่นขึ้นอย่างต่อเนื่องหรือไม่
ในขณะที่ (จริง) {ซิงโครไนซ์ (bread.class) {ในขณะที่ (! b.flag) {ลอง {bread.class.wait ();} catch (interruptedException i) {}} System.out.println (thread.currentthread (). getName ()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -ปัญหาความปลอดภัยแบบมัลติเธรดครั้งแรกได้รับการแก้ไข แต่ปัญหาการหยุดชะงักเกิดขึ้น นี่เป็นเรื่องง่ายที่จะวิเคราะห์ ผู้ผลิตได้รับการยกย่องว่าเป็นภาพรวมและผู้บริโภคก็รวมกันเช่นกัน เมื่อเธรดของผู้ผลิตกำลังรออยู่ (กระทู้ของฝ่ายผลิตถูกปลุกให้ตื่นขึ้นอย่างต่อเนื่องหัวข้อทั้งหมดของปาร์ตี้จะรอ) และผู้บริโภคก็รอคอยและการหยุดชะงักจะปรากฏขึ้น ในความเป็นจริงถ้าคุณดูในแบบขยายผู้ผลิตและผู้บริโภคจะได้รับการยกย่องว่าเป็นเธรดหนึ่งตามลำดับ สองเธรดนี้สร้างหลายเธรด เมื่อด้านหนึ่งรอและไม่สามารถปลุกอีกด้านหนึ่งอีกด้านหนึ่งจะรอแน่นอนดังนั้นมันจะถูกปิดกั้น
สำหรับปัญหาการหยุดชะงักระหว่างทั้งสองฝ่ายตราบใดที่คุณมั่นใจว่าอีกฝ่ายสามารถตื่นขึ้นมาได้มากกว่าการตื่นตัวอย่างต่อเนื่องของฝ่ายหนึ่งก็สามารถแก้ไขได้ เพียงใช้ NotifyAll () หรือ SignalAll () หรือคุณสามารถปลุกเธรดอื่น ๆ ผ่านสัญญาณ () เพื่อแก้ปัญหา ดูรหัสที่สองด้านล่าง
จากการวิเคราะห์ข้างต้นหากรหัสของการผลิตเดี่ยวและแบบจำลองการบริโภคเดี่ยวดีขึ้นจะสามารถเปลี่ยนเป็นแบบจำลองการผลิตหลายครั้งและแบบหลายครั้ง
// รหัสเซ็กเมนต์ 1 คลาสขนมปัง {ชื่อสตริงสาธารณะ; จำนวน int สาธารณะ = 1; ธงบูลีนสาธารณะ = เท็จ; } // อธิบายผู้ผลิตคลาสผู้ผลิตใช้งานได้ {ขนมปังส่วนตัว B; ผู้ผลิต (ขนมปัง b) {this.b = b; } โมฆะสาธารณะผลิต (ชื่อสตริง) {b.name = ชื่อ + b.count; B.Count ++; } โมฆะสาธารณะเรียกใช้ () {ในขณะที่ (จริง) {ซิงโครไนซ์ (bread.class) {ในขณะที่ (b.flag) {ลอง {bread.class.wait ();} catch (interruptedException i) {}} ผลิต ("ขนมปัง"); System.out.println (thread.currentthread (). getName ()+"-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - การบริโภคสตริง () {return b.name;} public void run () {ในขณะที่ (จริง) {ซิงโครไนซ์ (bread.class) {ในขณะที่ (! b.flag) {ลอง {bread.class.wait (); System.out.println (thread.currentthread (). getName ()+"-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - }} Producects Produceconsume_5 {public static main (String [] args) {// 1 เธรด con_t1 = ใหม่ (con);ต่อไปนี้เป็นรหัส refactored โดยใช้ Lock และ Conditon โดยใช้ Signal () เพื่อปลุกเธรดอื่น ๆ
// รหัสเซ็กเมนต์ 2Import java.util.concurrent.locks.*; ขนมปังคลาส {ชื่อสตริงสาธารณะ; จำนวน int สาธารณะ = 1; ธงบูลีนสาธารณะ = เท็จ; ล็อคแบบคงที่สาธารณะ = ใหม่ reentrantlock (); สภาพคงที่สาธารณะ pro_con = lock.newcondition (); สภาพคงที่สาธารณะ con_con = lock.newcondition ();} // อธิบายผู้ผลิตคลาสผู้ผลิตใช้งาน {ขนมปังส่วนตัว B; ผู้ผลิต (ขนมปัง b) {this.b = b; } โมฆะสาธารณะผลิต (ชื่อสตริง) {b.name = ชื่อ + b.count; B.Count ++; } public void run () {ในขณะที่ (จริง) {bread.lock.lock (); ลอง {ในขณะที่ (b.flag) {ลอง {bread.pro_con.await ();} catch (interruptedException i) {}} ผลิต ("ขนมปัง"); System.out.println (thread.currentthread (). getName ()+"-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Bread.con_con.signal (); bread.lock.lock (); ลอง {ในขณะที่ (! b.flag) {ลอง {bread.con_con.await (); System.out.println (thread.currentthread (). getName ()+"-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - // 2. con_t1.start ();มาสรุปประเด็นการผลิตที่มากขึ้นและการบริโภคที่มากขึ้น:
(1). วิธีการแก้ปัญหาการซิงโครไนซ์แบบมัลติเธรดของบุคคลบางฝ่ายคือการใช้ในขณะที่ (ธง) เพื่อตรวจสอบว่ารออยู่หรือไม่
(2). การแก้ปัญหาการหยุดชะงักของทั้งสองฝ่ายคือการปลุกอีกฝ่าย คุณสามารถใช้ NotifyAll (), SignalAll () หรือวิธีการสัญญาณ () ของจอภาพของอีกฝ่าย
6. โมเดลการผลิตและการบริโภคเพิ่มเติม
มีเธรดผู้ผลิตหลายตัวและเธรดผู้บริโภคหลายตัว ผู้ผลิตใส่ขนมปังที่ผลิตลงในตะกร้า (ชุดหรืออาร์เรย์) และผู้บริโภคจะนำขนมปังออกจากตะกร้า พื้นฐานสำหรับผู้ผลิตที่จะตัดสินการผลิตอย่างต่อเนื่องคือตะกร้าเต็มและพื้นฐานสำหรับผู้บริโภคในการตัดสินการบริโภคอย่างต่อเนื่องคือตะกร้าว่างเปล่าหรือไม่ นอกจากนี้เมื่อผู้บริโภคนำขนมปังออกตำแหน่งที่สอดคล้องกันจะว่างเปล่าอีกครั้งและผู้ผลิตสามารถหันหลังกลับและดำเนินการผลิตต่อจากตำแหน่งเริ่มต้นของตะกร้าซึ่งสามารถทำได้โดยการรีเซ็ตตัวชี้ของตะกร้า
ในโมเดลนี้นอกเหนือจากการอธิบายผู้ผลิตผู้บริโภคและขนมปังแล้วยังจำเป็นต้องอธิบายคอนเทนเนอร์ตะกร้า สมมติว่าอาร์เรย์ถูกใช้เป็นคอนเทนเนอร์ทุกครั้งที่ผู้ผลิตผลิตหนึ่งตัวชี้การผลิตจะเปลี่ยนไปข้างหลังและทุกครั้งที่ผู้บริโภคบริโภคหนึ่งตัวตัวชี้การบริโภคจะเปลี่ยนไปข้างหลัง
รหัสมีดังนี้: คุณสามารถอ้างถึงรหัสตัวอย่างที่ระบุในคลาส API->
นำเข้า java.util.concurrent.locks.*; ตะกร้าชั้น {ขนมปังส่วนตัว [] arr; // ขนาดของตะกร้าตะกร้า (ขนาด int) {arr = ขนมปังใหม่ [ขนาด]; } // ตัวชี้ของ INT ในและนอกส่วนตัว int in_ptr, out_ptr; // เหลือกี่ขนมปังในตะกร้าส่วนตัว int ซ้าย; ล็อคส่วนตัว = ใหม่ reentrantlock (); เงื่อนไขส่วนตัวเต็ม = lock.newCondition (); สภาพส่วนตัวว่างเปล่า = lock.newCondition (); // ขนมปังเข้าไปในตะกร้าโมฆะสาธารณะใน () {lock.lock (); ลอง {ในขณะที่ (ซ้าย == arr.length) {ลอง {full.await ();} catch (interruptedexception i) {i.printstacktrace ();}} arr [in_ptr] = ขนมปังใหม่ ("mianbao", producer.num ++); System.out.println ("ใส่ขนมปัง:"+arr [in_ptr] .getName ()+"------- ลงในตะกร้า ["+in_ptr+"]"); ซ้าย ++; if (++ in_ptr == arr.length) {in_ptr = 0;} emport.signal (); } ในที่สุด {lock.unlock (); }} // ขนมปังออกจาก Basket Bread Out () {lock.lock (); ลอง {ในขณะที่ (ซ้าย == 0) {ลอง {emport.await ();} catch (interruptedException i) {i.printStackTrace ();}} ขนมปัง out_bread = arr [out_ptr]; System.out.println ("รับขนมปัง:"+out_bread.getName ()+"---------- จากตะกร้า ["+out_ptr+"]"); ซ้าย--; if (++ out_ptr == arr.length) {out_ptr = 0;} full.signal (); กลับ out_bread; } ในที่สุด {lock.unlock (); }}} คลาสขนมปังคลาส {ชื่อสตริงส่วนตัว; ขนมปัง (ชื่อสตริง, int num) {this.name = name + num; } สตริงสาธารณะ getName () {return this.name; }} ผู้ผลิตในชั้นเรียนใช้งาน {ตะกร้าส่วนตัวที่รันได้ {ตะกร้าส่วนตัว; สาธารณะคงที่ int num = 1; // หมายเลขแรกสำหรับผู้ผลิตชื่อขนมปัง (ตะกร้า B) {this.basket = b; } โมฆะสาธารณะเรียกใช้ () {ในขณะที่ (จริง) {basket.in (); ลอง {thread.sleep (10);} catch (interruptedException i) {}}}} ผู้บริโภคในชั้นเรียนใช้งานการใช้งาน {ตะกร้าส่วนตัวตะกร้าส่วนตัว; ขนมปังส่วนตัว i_get; ผู้บริโภค (ตะกร้า b) {this.basket = b; } โมฆะสาธารณะเรียกใช้ () {ในขณะที่ (จริง) {i_get = basket.out (); ลอง {thread.sleep (10);} catch (interruptedException i) {}}}} คลาสสาธารณะ produceconsume_7 {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {ตะกร้า b = ตะกร้าใหม่ (20); // ขนาดตะกร้า = 20 Producer Pro = ผู้ผลิตใหม่ (B); Consumer Con = ผู้บริโภคใหม่ (B); เธรด PRO_T1 = เธรดใหม่ (PRO); เธรด PRO_T2 = เธรดใหม่ (PRO); เธรด con_t1 = เธรดใหม่ (con); เธรด con_t2 = เธรดใหม่ (con); เธรด con_t3 = เธรดใหม่ (con); pro_t1.start (); pro_t2.start (); con_t1.start (); con_t2.start (); con_t3.start (); -สิ่งนี้เกี่ยวข้องกับผู้บริโภคผู้ผลิตขนมปังและตะกร้าที่ขนมปังและตะกร้าเป็นทรัพยากรที่ดำเนินการโดยหลายเธรด ด้ายผู้ผลิตผลิตขนมปังและใส่ลงในตะกร้าและด้ายผู้บริโภคจะเอาขนมปังออกจากตะกร้า รหัสในอุดมคติคือการห่อหุ้มทั้งงานการผลิตและงานการบริโภคในคลาสทรัพยากร เนื่องจากขนมปังเป็นองค์ประกอบของภาชนะบรรจุตะกร้าจึงไม่เหมาะสำหรับบรรจุภัณฑ์ในชั้นเรียนขนมปังและบรรจุภัณฑ์ลงในตะกร้าทำให้สามารถใช้งานภาชนะได้ง่ายขึ้น
โปรดทราบว่าคุณต้องใส่รหัสทั้งหมดที่เกี่ยวข้องกับการดำเนินการทรัพยากรภายในการล็อคมิฉะนั้นปัญหาการซิงโครไนซ์แบบหลายเธรดจะเกิดขึ้น ตัวอย่างเช่นวิธีการผลิตขนมปังถูกกำหนดไว้ในคลาสผู้ผลิตและจากนั้นมันจะถูกใช้เป็นพารามิเตอร์กับวิธีการ basket.in () ใส่ลงในตะกร้าเช่น basket.in (producer ()) ซึ่งเป็นพฤติกรรมที่ผิดเนื่องจากผู้ผลิต () ถูกส่งผ่านไปยังวิธีการใน ()
บทความข้างต้นขึ้นอยู่กับผู้ผลิต Java และโมเดลผู้บริโภค (การวิเคราะห์โดยละเอียด) และเป็นเนื้อหาทั้งหมดที่ใช้ร่วมกันโดยบรรณาธิการ ฉันหวังว่ามันจะให้ข้อมูลอ้างอิงและฉันหวังว่าคุณจะสนับสนุน wulin.com มากขึ้น