Яку паралельну реалізацію черги я повинен використовувати на Java?


132

З JavaDocs:

  • ConcurrentLinkedQueue є підходящим вибором , коли багато потоків буде загальний доступ до загальної колекції. Ця черга не дозволяє нульових елементів.
  • ArrayBlockingQueue - класичний "обмежений буфер", в якому масив фіксованого розміру містить елементи, вставлені виробниками та витягнуті споживачами. Цей клас підтримує необов'язкову політику справедливості для замовлення очікуючих ниток для виробників та споживачів
  • Зазвичай LinkedBlockingQueue мають більшу пропускну здатність, ніж черги на основі масиву, але менш передбачувану продуктивність у більшості одночасних додатків.

У мене є 2 сценарії, одному потрібна черга, щоб підтримувати багатьох виробників (потоки, що використовують його) з одним споживачем, а інший - навпаки.

Я не розумію, яку реалізацію використовувати. Хтось може пояснити, в чому полягають відмінності?

Крім того, що таке "факультативна політика справедливості" у програмі ArrayBlockingQueue?


1
Ви також забули запитати про PriorityBlockingQueue, що корисно для визначення порядку, в якому обробляються потоки.
ІгорГанапольський

Відповіді:


53

В основному різниця між ними полягає в характеристиках продуктивності та блокуючій поведінці.

Найпростіше спочатку ArrayBlockingQueue- це черга з фіксованим розміром. Отже, якщо встановити розмір у 10 та спробувати вставити 11-й елемент, оператор вставлення блокується, поки інший потік не видалить елемент. Питання справедливості - це те, що відбувається, якщо декілька потоків намагаються вставити та видалити одночасно (іншими словами, протягом періоду, коли чергу була заблокована). Алгоритм справедливості гарантує, що перший потік, який запитує, є першим потоком, який отримує. В іншому випадку дана нитка може зачекати довше, ніж інші потоки, спричиняючи непередбачувану поведінку (іноді один потік займе декілька секунд, оскільки інші нитки, які розпочалися пізніше, обробляються спочатку). Компроміс полягає в тому, що для управління справедливістю потрібні накладні витрати, уповільнення пропускної здатності.

Найважливіша відмінність LinkedBlockingQueueі ConcurrentLinkedQueueполягає в тому, що якщо ви запитаєте елемент з а, LinkedBlockingQueueа черга порожня, ваш потік буде чекати, поки там щось з’явиться. A ConcurrentLinkedQueueповернеться відразу з поведінкою порожньої черги.

Від яких залежить, чи потрібно блокування. Там, де у вас багато виробників і один споживач, це звучить так. З іншого боку, там, де у вас багато споживачів і лише один виробник, вам може не знадобитися блокуюча поведінка, і, можливо, вони будуть щасливі просто перевірити, чи черга порожня, і рухатися далі, якщо вона є.


67
Відповідь вводить в оману. І LinkedBlockingQueue, і ConcurrentLinkedQueue мають метод "опитування ()", який видаляє заголовок черги або повертає нуль (не блокує) та метод "пропозиція (E e)", який вставляється у хвіст черги і не блокується. Різниця полягає в тому, що лише LinkedBlockingQueue крім операцій, що блокують, окрім блокуючих операцій - і за цей привілей ви платите ціну, яку фактично має деяке блокування. Інша відповідь пояснює це.
Голий

123

ConcurrentLinkedQueue означає, що блокування не приймається (тобто не синхронізовані (це) або Lock.lock дзвінки). Він використовуватиме CAS - Порівняти та заміняти операцію під час модифікацій, щоб побачити, чи вузол голова / хвіст все ще такий, як коли він запускався. Якщо так, операція проходить успішно. Якщо вузол голова / хвіст відрізняється, він обернеться і спробує ще раз.

LinkedBlockingQueue зніме блокування перед будь-якими змінами. Тож ваші дзвінки з пропозиціями будуть заблоковані, поки вони не заблокують. Ви можете використовувати перевантаження пропозицій, яке вимагає TimeUnit, щоб сказати, що ви готові зачекати X кількість часу, перш ніж відмовитись від додавання (зазвичай це добре для черг типу повідомлень, де повідомлення застаріло після X числа мілісекунд).

Справедливість означає, що реалізація Lock збереже впорядковані потоки. Значення, якщо нитка A увійде, а потім нитка B, нитка A спочатку отримає замок. Без справедливості не визначено, що дійсно відбувається. Це, швидше за все, буде наступною темою, яка планується.

Щодо того, який використовувати, це залежить. Я схильний використовувати ConcurrentLinkedQueue, оскільки час, який потрібен моїм виробникам, щоб отримати роботу, щоб поставити на чергу, різноманітний. У мене не так багато виробників, які виробляють в той самий момент. Але споживча сторона є більш складною, оскільки опитування не перейде в приємний сон. Ви повинні самі впоратися з цим.


1
І за яких умов ArrayBlockingQueue краще, ніж LinkedBlockingQueue?
колобок

@akapelko ArrayBlockingQueue забезпечує більш дрібне замовлення.
ІгорГанапольський

2
Що це означає - "він обернеться і спробує знову". ?
Лестер

9

Заголовок Вашого питання згадує Блокування черг. Однак ConcurrentLinkedQueueце не блокує чергу.

В BlockingQueues є ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueue, і SynchronousQueue.

Деякі з них явно не підходить для ваших цілей ( DelayQueue, PriorityBlockingQueueі SynchronousQueue). LinkedBlockingQueueіLinkedBlockingDeque ідентичні, за винятком того, що остання є двобічною чергою (вона реалізує інтерфейс Deque).

Оскільки ArrayBlockingQueueкорисний лише, якщо ви хочете обмежити кількість елементів, я б дотримувався LinkedBlockingQueue.


Я вилучив із заголовка слово Блокування, дякую. Дозвольте мені побачити, якщо я отримав це, що ви сказали, означає, що LinkedBlockingQueue можна використовувати в кількох споживачах / виробляє сценарії для одного об'єкта?
Девід Хофманн

1
Я думав, що ArrayBlockingQueue дозволяє зробити більш тонке впорядкування ниток? Звідси і його перевага.
ІгорГанапольський

4

ArrayBlockingQueue має менший слід пам’яті, він може повторно використовувати елемент вузла, не як LinkedBlockingQueue, який повинен створити об’єкт $ Node LinkedBlockingQueue для кожної нової вставки.


1
гарна думка! я віддаю перевагу ArrayBlockingQueue, ніж LinkedBlockingQueue
трильйони

2
Це необов'язково вірно - якщо ваша черга близько до порожнього часу, але вона має бути здатною до збільшення, ArrayBlockingQueueвона матиме набагато гірший слід пам’яті - вона все ще має великий масив, виділений у пам'яті весь час, тоді як LinkedBlockingQueueматиме незначний обсяг пам'яті , коли близько до порожньої.
Креаз

1
  1. SynchronousQueue(З іншого питання )

SynchronousQueueце більше передача, тоді як LinkedBlockingQueueсправедлива дозволяє отримати один елемент. Різниця полягає в тому, що put()виклик до 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 (реалізація масиву)

Конструктор для черги ArrayBlockingQue

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зрозуміла, одна має базову структуру даних Array та інші пов'язані списки .

ArrayBlockingQueueвикористовує алгоритм подвійної умовності з одним блокуванням і LinkedBlockingQueueє варіантом алгоритму "дві черги блокування" і він має 2 блокування 2 умови (takeLock, putLock)


0

ConcurrentLinkedQueue не заблоковано, LinkedBlockingQueue - ні. Кожен раз, коли ви викликаєте LinkedBlockingQueue.put () або LinkedBlockingQueue.take (), спочатку потрібно придбати блокування. Іншими словами, LinkedBlockingQueue має слабку конкурентоспроможність. Якщо вам важлива продуктивність, спробуйте ConcurrentLinkedQueue + LockSupport.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.