ตัวอย่าง SynchronousQueue ใน Java - การแก้ปัญหา Producer Consumer
SynchronousQueue เป็น BlockingQueue ชนิดพิเศษที่การดำเนินการแทรกแต่ละรายการต้องรอคำสั่งลบที่เกี่ยวข้องในเธรดอื่น และในทางกลับกัน เมื่อคุณเรียกใช้เมธอด put()บน SynchronousQueue มันจะบล็อกจนกว่าเธรดอื่นจะนำองค์ประกอบนั้นไป ดังนั้น หากเธรดอื่นพยายามลบองค์ประกอบออกจากเธรดนั้น และไม่มีองค์ประกอบนั้นอยู่ เธรดนั้นจะบล็อกจนกว่าเธรดอื่นจะวางองค์ประกอบนั้นไว้ในคิว คุณสามารถนึกถึง SynchronousQueue ในฐานะนักกีฬา ( thread ) ที่วิ่งด้วยคบเพลิงโอลิมปิก เขาวิ่งด้วยคบเพลิง (วัตถุที่กำลังส่งต่อ) และส่งต่อไปยังนักกีฬาอีกคนที่รออยู่อีกด้านหนึ่ง หากคุณใส่ใจกับชื่อ คุณจะเข้าใจว่า SynchronousQueue ได้รับการตั้งชื่อเช่นนั้นด้วยเหตุผล โดยจะถ่ายโอนข้อมูลแบบซิงโครนัสไปยังเธรดอื่น มันรอให้ใครบางคนรับข้อมูลแทนที่จะใส่เข้าและออก (การดำเนินการแบบอะซิงโครนัส) หากคุณคุ้นเคยกับ CSP และ Ada คุณจะรู้ว่าคิวที่ซิงโครไนซ์นั้นคล้ายคลึงกับการประชุมของเธรด เหมาะอย่างยิ่งสำหรับโครงสร้างการถ่ายโอนการควบคุมซึ่งอ็อบเจ็กต์ที่ทำงานในเธรดหนึ่งต้องซิงโครไนซ์กับอ็อบเจ็กต์ในอีกเธรดหนึ่งเพื่อส่งข้อมูล เหตุการณ์ หรืองานบางอย่างไปให้ ในบท ช่วยสอนการเขียนโปรแกรมแบบมัลติเธรดก่อนหน้านี้ เราได้เรียนรู้วิธีการแก้ปัญหาผู้ผลิต-ผู้บริโภคโดยใช้ เมธอด wait and notifyและBlockingQueue ตอนนี้เราจะได้เรียนรู้วิธีใช้รูปแบบผู้ผลิต-ผู้บริโภคโดยใช้ SynchronousQueue คลาสนี้ยังสนับสนุนพฤติกรรมที่เป็นธรรมในการสั่งซื้อการรอของเธรดของผู้ผลิตและผู้บริโภค ตามค่าเริ่มต้น จะไม่รับประกันการสั่งซื้อนี้ อย่างไรก็ตาม คิวที่สร้างขึ้นด้วยคุณสมบัติยุติธรรมจะรับประกันการเข้าถึงเธรดในคิว FIFO (Firs In First Out)ผู้ผลิต/ผู้บริโภคที่ใช้ SynchronousQueue ใน Java
ดังที่ฉันได้กล่าวไว้ข้างต้น ไม่มีอะไรดีไปกว่าปัญหาระหว่างผู้ผลิตและผู้บริโภคในการทำความเข้าใจการสื่อสารระหว่างเธรดในภาษาการเขียนโปรแกรมใดๆ ในปัญหานี้ เธรดหนึ่งทำหน้าที่เป็นผู้ผลิตที่สร้างเหตุการณ์และงาน และเธรดอื่นทำหน้าที่เป็นผู้บริโภค บัฟเฟอร์ที่ใช้ร่วมกันใช้เพื่อถ่ายโอนข้อมูลจากผู้ผลิตไปยังผู้บริโภค ความยากในการแก้ปัญหานี้เกิดขึ้นในกรณีร้ายแรง เช่น เมื่อผู้ผลิตถูกบังคับให้รอเพราะ... บัฟเฟอร์เต็มหรือผู้บริโภคถูกบังคับให้รอเนื่องจาก บัฟเฟอร์ว่างเปล่า เรื่องนี้แก้ไขได้ง่ายเพราะ... คิวการบล็อกไม่เพียงแต่จัดเตรียมบัฟเฟอร์สำหรับการจัดเก็บข้อมูลเท่านั้น แต่ยังรวมถึงการควบคุมการไหล การบล็อกเธรดที่เรียกใช้เมธอด put() (Producer) หากบัฟเฟอร์เต็ม และการบล็อกเธรดที่เรียกใช้เมธอด take() (Consumer) หาก บัฟเฟอร์ว่างเปล่า ตอนนี้เราจะแก้ไขปัญหาเดียวกันนี้โดยใช้ SynchronousQueue ซึ่งเป็น คอลเลกชันแบบขนานชนิดพิเศษที่มีความจุเป็นศูนย์ ในตัวอย่างต่อไปนี้ เรามีสองเธรดที่เรียกว่าPRODUCERและCONSUMER (ตั้งชื่อให้กับเธรดเสมอซึ่งเป็นรูปแบบที่ดีมากของการเขียนโปรแกรมแบบมัลติเธรด) เธรดแรกโพสต์คะแนนในเกม และเธรดที่สองจะใช้มัน คะแนนในเกมไม่มีอะไรมากไปกว่าวัตถุประเภทสตริง แต่ถ้าคุณรันโปรแกรมด้วยประเภทอื่น คุณจะไม่สังเกตเห็นความแตกต่างใดๆ เพื่อทำความเข้าใจวิธีการทำงานของ SynchronousQueue และวิธีแก้ปัญหาผู้ผลิต-ผู้บริโภค คุณต้อง: รันโปรแกรมสำหรับการดีบัก (ดีบัก) ในสภาพแวดล้อม Eclipseหรือเพียงแค่เริ่มเธรดผู้ผลิตโดยใส่เครื่องหมายความคิดเห็น Consumer.start(); หากเธรดผู้บริโภคไม่ได้ทำงานอยู่ เธรดผู้ผลิตจะถูกบล็อกที่ Queu.put(event); หากทำงานอยู่ คุณจะไม่เห็นผู้ผลิต [PRODUCER] เผยแพร่กิจกรรม :FOUR สิ่งนี้เกิดขึ้นเพราะว่า พฤติกรรมเฉพาะของ SynchronousQueue ซึ่งช่วยให้มั่นใจว่าข้อมูลการโพสต์เธรดจะถูกบล็อกจนกว่าเธรดอื่นจะรับข้อมูลไป และในทางกลับกัน คุณสามารถทดสอบโค้ดที่เหลือได้โดยการใส่ความคิดเห็นที่producer.start(); และเริ่มต้นเฉพาะเธรดผู้บริโภคเท่านั้น หากคุณศึกษาอย่างรอบคอบว่าโปรแกรมส่งออกอะไรออกมา คุณจะสังเกตเห็นว่าลำดับของเอาต์พุตกลับกัน ดูเหมือนว่า เธรด [CONSUMER] จะนำข้อมูลก่อนที่ เธรด [PRODUCER]จะสร้างขึ้นมา เนื่องจาก SynchronousQueue ไม่รับประกันการเข้าคิวตามค่าเริ่มต้น แต่มีกฎความเป็นธรรมที่กำหนดการเข้าถึงเธรดตามลำดับ FIFO คุณสามารถเปิดใช้งานกฎเหล่านี้ได้โดยการส่งค่าจริงไปยัง ตัวสร้าง SynchronousQueue ที่โอเวอร์โหลดดังนี้: import java.util.concurrent.SynchronousQueue; /** * Java Program to solve Producer Consumer problem using SynchronousQueue. A * call to put() will block until there is a corresponding thread to take() that * element. * * @author Javin Paul */ public class SynchronousQueueDemo{ public static void main(String args[]) { final SynchronousQueue
queue = new SynchronousQueue
(); Thread producer = new Thread("PRODUCER") { public void run() { String event = "FOUR"; try { queue.put(event); // thread will block here System.out.printf("[%s] published event : %s %n", Thread .currentThread() .getName(), event); } catch (InterruptedException e) { e.printStackTrace(); } } }; producer.start(); // starting publisher thread Thread consumer = new Thread("CONSUMER") { public void run() { try { String event = queue.take(); // thread will block here System.out.printf("[%s] consumed event : %s %n", Thread .currentThread() .getName(), event); } catch (InterruptedException e) { e.printStackTrace(); } } }; consumer.start(); // starting consumer thread } } Output: [CONSUMER] consumed event : FOUR [PRODUCER] published event : FOUR
new SynchronousQueue(boolean fair).
สิ่งที่คุณต้องจำเกี่ยวกับ SynchronousQueue ใน Java
ต่อไปนี้เป็นคุณสมบัติที่สำคัญบางประการของคิวการบล็อกชนิดพิเศษใน Java มีประโยชน์มากในการส่งข้อมูลจากเธรดหนึ่งไปยังอีกเธรดหนึ่งในลักษณะซิงโครไนซ์ คิวนี้ไม่มีความสามารถและถูกบล็อกจนกว่าเธรดอื่นจะว่าง
- SynchronousQueue บล็อก และจนกว่าเธรดหนึ่งพร้อมที่จะรับข้อมูล อีกเธรดจะพยายามใส่ข้อมูล
- SynchronousQueue ไม่มีโวลุ่ม นั่นคือไม่มีข้อมูล
- SynchronousQueue ใช้เพื่อใช้กลยุทธ์การส่งต่อคิว โดยที่เธรดส่งผ่านการควบคุมไปยังเธรดที่รอ หรือสร้างขึ้นใหม่หากได้รับอนุญาต มิฉะนั้น การควบคุมจะไม่ถูกถ่ายโอน
- คิวนี้ไม่อนุญาตให้มีข้อมูลว่าง ความพยายามที่จะเพิ่มองค์ประกอบ null จะทำให้เกิดNullPointerException
- หากคุณใช้วิธีการอื่นจากคอลเลกชัน (เช่น มี) SynchronousQueue จะทำงานเหมือนกับคอลเลกชันเปล่า
- คุณไม่สามารถใช้วิธี peek ของ SynchronousQueue ได้เนื่องจากองค์ประกอบมีอยู่เฉพาะเมื่อคุณพยายามลบออกเท่านั้น นอกจากนี้ คุณจะไม่สามารถแทรกองค์ประกอบได้ (โดยใช้วิธีการใดๆ) จนกว่าเธรดอื่นจะพยายามลบออก
- คุณจะไม่สามารถใช้ตัววนซ้ำสำหรับ SynchronousQueue ได้เนื่องจาก... มันไม่มีองค์ประกอบ
- SynchronousQueue สามารถสร้างได้ด้วยกฎที่ยุติธรรม ซึ่งรับประกันการเข้าถึงเธรดตามลำดับ FIFO
GO TO FULL VERSION