LinkedBlockingQueue เทียบกับ ConcurrentLinkedQueue


112

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

ข้อดี / ข้อเสียของการใช้อย่างอื่นคืออะไร?

ความแตกต่างหลักที่ฉันเห็นได้จากมุมมองของ API คือLinkedBlockingQueueสามารถกำหนดขอบเขตได้

คำตอบ:


110

สำหรับเธรดผู้ผลิต / ผู้บริโภคฉันไม่แน่ใจว่านั่นConcurrentLinkedQueueเป็นตัวเลือกที่สมเหตุสมผล - มันไม่ได้ใช้งานBlockingQueueซึ่งเป็นอินเทอร์เฟซพื้นฐานสำหรับคิวผู้ผลิต / ผู้บริโภค IMO คุณต้องโทรไปpoll()รอสักครู่หากคุณไม่พบอะไรแล้วสำรวจความคิดเห็นอีกครั้ง ฯลฯ ... ซึ่งนำไปสู่ความล่าช้าเมื่อมีรายการใหม่เข้ามาและความไร้ประสิทธิภาพเมื่อว่างเปล่า (เนื่องจากตื่นขึ้นมาโดยไม่จำเป็น) .

จากเอกสารสำหรับ BlockingQueue:

BlockingQueue การใช้งานได้รับการออกแบบมาเพื่อใช้สำหรับคิวผู้ผลิต - ผู้บริโภคเป็นหลัก

ฉันรู้ว่ามันไม่ได้บอกอย่างเคร่งครัดว่าควรใช้เฉพาะคิวการบล็อกสำหรับคิวผู้ผลิต - ผู้บริโภค แต่ถึงอย่างนั้น ...


4
ขอบคุณจอน - ฉันไม่ได้สังเกตเห็นสิ่งนั้น แล้วทำไมคุณถึงใช้ ConcurrentLinkedQueue?
Adamski

27
เมื่อคุณต้องการเข้าถึงคิวจากเธรดจำนวนมาก แต่คุณไม่จำเป็นต้อง "รอ" ในนั้น
Jon Skeet

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

กรณีของคุณเท่านั้นถือเป็นจริงถ้าเราใช้boundedคิวในมากมายคิวtake()และput()เพียงสิ้นเปลืองทรัพยากรพิเศษ (interms ConcurrentLinkedQueue ของการประสานกว่า) แม้ว่ามันจะเป็นกรณีที่จะใช้คิวกระโดดสำหรับสถานการณ์การผลิตของผู้บริโภค
Amarnath Harish

@Adamski IMO, ConcurrentLinkedQueue เป็นเพียงรายการที่เชื่อมโยงที่จะใช้ในสภาพแวดล้อมแบบมัลติเธรด การเปรียบเทียบที่ดีที่สุดสำหรับสิ่งนี้คือ ConcurrentHashMap และ HashMap
Nishit

69

คำถามนี้สมควรได้รับคำตอบที่ดีกว่า

Java ConcurrentLinkedQueueขึ้นอยู่กับอัลกอริทึมที่มีชื่อเสียงของ Maged M. Michael และ Michael L. Scottสำหรับคิวที่ไม่มีการล็อคแบบไม่ปิดกั้น

"ไม่บล็อก" เป็นคำศัพท์สำหรับทรัพยากรที่ขัดแย้งกัน (คิวของเรา) หมายความว่าไม่ว่าตัวกำหนดตารางเวลาของแพลตฟอร์มจะทำอะไรเช่นการขัดจังหวะเธรดหรือหากเธรดที่เป็นปัญหาช้าเกินไปเธรดอื่น ๆ ที่ต่อสู้เพื่อทรัพยากรเดียวกัน จะยังคงสามารถดำเนินการต่อไปได้ หากเกี่ยวข้องกับการล็อกเช่นเธรดที่ยึดล็อกอาจถูกขัดจังหวะและเธรดทั้งหมดที่รอการล็อกนั้นจะถูกบล็อก Intrinsic locks ( synchronizedคีย์เวิร์ด) ใน Java อาจมาพร้อมกับบทลงโทษที่รุนแรงสำหรับประสิทธิภาพเช่นเมื่อล็อคเอนเอียงมีส่วนเกี่ยวข้องและคุณมีความขัดแย้งหรือหลังจาก VM ตัดสินใจที่จะ "ขยาย" การล็อกหลังจากระยะเวลาผ่อนผันการหมุนและบล็อกเธรดที่แข่งขันกัน ... ซึ่งเป็นเหตุผลว่าทำไมในหลาย ๆ บริบท (สถานการณ์ที่มีความขัดแย้งน้อย / ปานกลาง) ทำการเปรียบเทียบและ ชุดข้อมูลอ้างอิงอะตอมมีประสิทธิภาพมากขึ้นและนี่คือสิ่งที่โครงสร้างข้อมูลที่ไม่ปิดกั้นจำนวนมากกำลังทำอยู่

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

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


8
ตามย่อหน้าสุดท้ายของคุณทำไมคุณถึงบอกว่าการรอคิวเป็นวิธีที่แย่มากในการออกแบบระบบพร้อมกัน ถ้าเรามีกลุ่มเธรดที่มี 10 เธรดที่กินงานจากคิวงานจะมีอะไรผิดปกติกับการบล็อกเมื่อคิวงานมีน้อยกว่า 10 งาน
Pacerier

11
@AlexandruNedelcu คุณไม่สามารถทำให้คำสั่งกวาดเช่น "ประหลาดที่น่ากลัว" ที่มากมักจะกรอบมากนักแสดงที่คุณพูดกับการใช้การใช้ threadpools ซึ่งตัวเองคุณBlockingQueue 's หากคุณต้องการระบบที่มีปฏิกิริยาตอบสนองสูงและคุณรู้วิธีจัดการกับแรงดันย้อนกลับ (สิ่งที่การปิดกั้นคิวลดลง) ดีกว่าการไม่ปิดกั้นนั้นเหนือกว่าอย่างชัดเจน แต่ .. บ่อยครั้งที่การบล็อก IO และคิวการบล็อกสามารถดำเนินการแบบ nonblocking ได้โดยเฉพาะอย่างยิ่งหากคุณมีงานที่ต้องใช้ IO เป็นเวลานานและไม่สามารถแบ่ง n 'เอาชนะได้
Adam Gent

1
@AdamGent - กรอบงานของนักแสดงมีการใช้งานกล่องจดหมายตามคิวการบล็อก แต่นั่นเป็นข้อบกพร่องในความคิดของฉันเนื่องจากการบล็อกไม่ทำงานในขอบเขตแบบอะซิงโครนัสจึงใช้ได้เฉพาะในการสาธิตเท่านั้น สำหรับฉันนี่เป็นที่มาของความไม่พอใจเช่นความคิดของ Akka ในการจัดการกับการล้นคือการบล็อกแทนที่จะส่งข้อความจนถึงเวอร์ชัน 2.4 ซึ่งยังไม่ออก ที่กล่าวว่าฉันไม่เชื่อว่ามีกรณีการใช้งานที่คิวการบล็อกจะเหนือกว่า คุณกำลังรวมสองสิ่งที่ไม่ควรรวมเข้าด้วยกัน ฉันไม่ได้พูดเกี่ยวกับการบล็อก I / O
Alexandru Nedelcu

1
@AlexandruNedelcu ในขณะที่ฉันเห็นด้วยกับคุณในเรื่องแรงดันย้อนกลับฉันยังไม่เห็นระบบ "ล็อคฟรี" จากบนลงล่าง ที่ไหนสักแห่งในเทคโนโลยีกองซ้อนไม่ว่าจะเป็น Node.js, Erlang, Golang โดยใช้กลยุทธ์การรอบางประเภทไม่ว่าจะเป็นบล็อกคิว (ล็อก) หรือ CAS หมุนการบล็อกและบางกรณีกลยุทธ์การล็อกแบบเดิมจะเร็วกว่า มันยากมากที่จะไม่มีการล็อกเนื่องจากความสม่ำเสมอและนี่เป็นสิ่งสำคัญอย่างยิ่งกับการบล็อก io และตัวกำหนดตารางเวลาซึ่งเป็น ~ Producer / Consumer ForkJoinPool ใช้งานได้กับงานวิ่งระยะสั้นและยังคงมีการล็อกการหมุน CAS
Adam Gent

1
@AlexandruNedelcu ฉันเดาว่าคุณสามารถแสดงให้ฉันเห็นว่าคุณสามารถใช้ ConcurrentLinkedQueue ได้อย่างไร (ซึ่งไม่มีขอบเขต btw ด้วยเหตุนี้อาร์กิวเมนต์ backpressure ที่อ่อนแอของฉัน) สำหรับรูปแบบ Producer / Consumer ซึ่งเป็นรูปแบบที่จำเป็นสำหรับตัวกำหนดตารางเวลาและเธรดพูลฉันคิดว่าฉันจะให้และยอมรับว่า ไม่ควรใช้ BlockingQueue (และคุณไม่สามารถโกงและมอบหมายให้ทำอย่างอื่นในการจัดตารางเวลาได้เช่น akka เนื่องจากจะทำการบล็อก / รอในขณะที่เป็นผู้ผลิต / ผู้บริโภค)
Adam Gent

33

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

ConcurrentLinkedQueueไม่ได้ใช้การล็อก แต่CASในการดำเนินการวาง / ดำเนินการอาจช่วยลดความขัดแย้งกับเธรดผู้ผลิตและผู้บริโภคจำนวนมาก แต่การเป็นโครงสร้างข้อมูล "รอฟรี" ConcurrentLinkedQueueจะไม่บล็อกเมื่อว่างเปล่าหมายความว่าผู้บริโภคจะต้องจัดการกับค่าที่take()ส่งคืนnullโดย "ไม่ว่างรอ" เช่นเธรดผู้บริโภคกิน CPU

ดังนั้นอันไหน "ดีกว่า" จึงขึ้นอยู่กับจำนวนเธรดของผู้บริโภคอัตราการบริโภค / การผลิต ฯลฯ จำเป็นต้องมีเกณฑ์มาตรฐานสำหรับแต่ละสถานการณ์

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


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

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

โอเค แต่ถ้าunbounded blockingqueueใช้จะดีกว่าCASตามพร้อมกันConcurrentLinkedQueue
amarnath harish

@orodbhen การนอนหลับจะไม่ช่วยลดการสูญเสีย ระบบปฏิบัติการต้องทำงานหนักมากเพื่อให้เธรดออกจากโหมดสลีปและกำหนดเวลาและรันได้ หากยังไม่มีข้อความแสดงว่าระบบปฏิบัติการของคุณทำไปโดยเปล่าประโยชน์ ฉันอยากจะแนะนำให้ใช้ BlockingQueue จะดีกว่าเนื่องจากได้รับการออกแบบมาโดยเฉพาะสำหรับปัญหาผู้ผลิต - ผู้บริโภค
Nishit

จริงๆแล้วฉันสนใจส่วน "อัตราการบริโภค / ผลผลิต" มากดังนั้นอันไหนดีกว่าถ้าอัตราสูงไป
workplaylifecycle

0

อีกวิธีหนึ่ง (ที่ปรับขนาดได้ไม่ดี) คือช่องทางการนัดพบ: java.util.concurrent SynchronousQueue


0

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

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