ฉันควรใช้คิวใดพร้อมกันใน Java


132

จาก JavaDocs:

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

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

ฉันไม่เข้าใจว่าจะใช้การนำไปใช้งานใด ใครสามารถอธิบายความแตกต่างคืออะไร?

นอกจากนี้ 'นโยบายความเป็นธรรมทางเลือก' ArrayBlockingQueueคืออะไรใน?


1
คุณลืมถามเกี่ยวกับ PriorityBlockingQueue ด้วยซึ่งมีประโยชน์สำหรับการระบุลำดับที่เธรดได้รับการประมวลผล
IgorGanapolsky

คำตอบ:


53

โดยทั่วไปความแตกต่างระหว่างลักษณะเหล่านี้คือลักษณะการทำงานและพฤติกรรมการบล็อก

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

ความแตกต่างที่สำคัญที่สุดระหว่างLinkedBlockingQueueและConcurrentLinkedQueueคือถ้าคุณขอองค์ประกอบจาก a LinkedBlockingQueueและคิวว่างเปล่าเธรดของคุณจะรอจนกว่าจะมีบางอย่างอยู่ที่นั่น A ConcurrentLinkedQueueจะกลับมาทันทีพร้อมกับพฤติกรรมของคิวว่าง

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


67
คำตอบทำให้เข้าใจผิด ทั้ง LinkedBlockingQueue และ ConcurrentLinkedQueue มีเมธอด "โพลล์ ()" ซึ่งจะลบส่วนหัวของคิวหรือส่งคืนค่าว่าง (ไม่บล็อก) และเมธอด "offer (E e)" ซึ่งแทรกในส่วนท้ายของคิวและไม่บล็อก ความแตกต่างก็คือมีเพียง LinkedBlockingQueue เท่านั้นที่มีการบล็อกการดำเนินการนอกเหนือจากการดำเนินการที่ไม่ปิดกั้น - และสำหรับสิทธิพิเศษนั้นคุณจะจ่ายในราคาที่ LinkedBlockingQueue มีการล็อกบางอย่าง คำตอบอื่นอธิบายเรื่องนี้
เปลือย

123

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

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

ความเป็นธรรมหมายความว่าการใช้งาน Lock จะทำให้เธรดมีลำดับ หมายถึงถ้าเธรด A เข้าแล้วเธรด B เข้าเธรด A จะได้รับการล็อกก่อน ด้วยความไม่ยุติธรรมจึงไม่สามารถระบุได้ว่าเกิดอะไรขึ้น มักจะเป็นเธรดถัดไปที่ได้รับการกำหนดเวลา

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


1
และภายใต้เงื่อนไขอะไร ArrayBlockingQueue ดีกว่า LinkedBlockingQueue?
kolobok

@akapelko ArrayBlockingQueue ช่วยให้สามารถสั่งซื้อแบบละเอียดได้มากขึ้น
IgorGanapolsky

2
หมายความว่าอย่างไร - "มันจะหมุนไปรอบ ๆ แล้วลองอีกครั้ง" ?
Lester

9

หัวข้อคำถามของคุณกล่าวถึงคิวการบล็อก แต่ConcurrentLinkedQueueเป็นไม่ได้คิวการปิดกั้น

BlockingQueues มีArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, และPriorityBlockingQueueSynchronousQueue

บางส่วนของเหล่านี้จะเห็นได้ชัดว่าไม่เหมาะสำหรับวัตถุประสงค์ของคุณ ( DelayQueue, PriorityBlockingQueueและSynchronousQueue) LinkedBlockingQueueและLinkedBlockingDequeเหมือนกันยกเว้นว่าหลังเป็น Queue แบบ double-ended (ใช้อินเทอร์เฟซ Deque)

เนื่องจากArrayBlockingQueueมีประโยชน์เฉพาะในกรณีที่คุณต้องการ จำกัด จำนวนองค์ประกอบฉันจะยึดLinkedBlockingQueueตาม


ฉันลบคำว่า Blocking ออกจากชื่อเรื่องขอบคุณ ให้ฉันดูว่าฉันได้รับสิ่งที่คุณพูดหมายความว่า LinkedBlockingQueue สามารถใช้กับผู้บริโภคหลายราย / สร้างสถานการณ์บนวัตถุเดียวกันได้หรือไม่
David Hofmann

1
ฉันคิดว่า ArrayBlockingQueue ช่วยให้สามารถจัดลำดับเธรดแบบละเอียดได้มากขึ้น? ดังนั้นประโยชน์ของมัน
IgorGanapolsky

4

ArrayBlockingQueue มีหน่วยความจำที่ต่ำกว่าสามารถใช้โหนดองค์ประกอบซ้ำได้ไม่เหมือนกับ LinkedBlockingQueue ที่ต้องสร้างวัตถุ LinkedBlockingQueue $ Node สำหรับการแทรกใหม่แต่ละครั้ง


1
จุดดี! ฉันชอบ ArrayBlockingQueue มากกว่า LinkedBlockingQueue
trillions

2
สิ่งนี้ไม่จำเป็นต้องเป็นจริง - หากคิวของคุณใกล้จะว่างเปล่าเป็นเวลานาน แต่จำเป็นต้องสามารถขยายใหญ่ขึ้นArrayBlockingQueueได้ก็จะมีหน่วยความจำที่แย่ลงมาก - ยังคงมีอาร์เรย์ขนาดใหญ่ที่จัดสรรไว้ในหน่วยความจำตลอดเวลาในขณะที่LinkedBlockingQueueจะมีรอยความทรงจำเล็กน้อยเมื่อใกล้จะว่าง
Krease

1
  1. SynchronousQueue(นำมาจากคำถามอื่น)

SynchronousQueueเป็น handoff มากกว่าในขณะที่LinkedBlockingQueuejust อนุญาตองค์ประกอบเดียว ความแตกต่างคือการput()โทรไปยัง a SynchronousQueueจะไม่กลับมาจนกว่าจะมีการtake()โทรที่ตรงกันแต่มีLinkedBlockingQueueขนาด 1 การput()โทร (ไปยังคิวว่าง) จะกลับมาทันที โดยพื้นฐานแล้วเป็นการBlockingQueueใช้งานเมื่อคุณไม่ต้องการคิวจริงๆ (คุณไม่ต้องการรักษาข้อมูลที่รอดำเนินการ)

  1. LinkedBlockingQueue (LinkedListการนำไปใช้งาน แต่ไม่ตรงกับการใช้งาน JDK LinkedListโดยใช้โหนดชั้นในแบบคงที่เพื่อรักษาการเชื่อมโยงระหว่างองค์ประกอบ)

ตัวสร้างสำหรับ LinkedBlockingQueue

public LinkedBlockingQueue(int capacity) 
{
        if (capacity < = 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node< E >(null);   // Maintains a underlying linkedlist. ( Use when size is not known )
}

คลาสโหนดที่ใช้ในการดูแลลิงค์

static class Node<E> {
    E item;
    Node<E> next;
    Node(E x) { item = x; }
}

3. ArrayBlockingQueue (การใช้งาน Array)

ตัวสร้างสำหรับ ArrayBlockingQueue

public ArrayBlockingQueue(int capacity, boolean fair) 
{
            if (capacity < = 0)
                throw new IllegalArgumentException();
            this.items = new Object[capacity]; // Maintains a underlying array
            lock = new ReentrantLock(fair);
            notEmpty = lock.newCondition();
            notFull =  lock.newCondition();
}

IMHO ความแตกต่างที่ใหญ่ที่สุดระหว่างArrayBlockingQueueและLinkedBlockingQueueชัดเจนจากตัวสร้างที่มีพื้นฐานอาร์เรย์โครงสร้างข้อมูลและ LinkedList

ArrayBlockingQueueใช้อัลกอริทึมเงื่อนไขคู่แบบ single-lockและLinkedBlockingQueueเป็นตัวแปรของอัลกอริทึม "two lock que" และมี 2 locks 2 condition (takeLock, putLock)


0

ConcurrentLinkedQueue ไม่มีการล็อกไม่มี LinkedBlockingQueue ทุกครั้งที่คุณเรียกใช้ LinkedBlockingQueue.put () หรือ LinkedBlockingQueue.take () คุณต้องได้รับการล็อกก่อน กล่าวอีกนัยหนึ่ง LinkedBlockingQueue มีภาวะพร้อมกันที่ไม่ดี หากคุณสนใจประสิทธิภาพลอง ConcurrentLinkedQueue + LockSupport

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