Какую реализацию параллельной очереди мне следует использовать в Java?

Из JavaDocs:

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

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

Я не понимаю, какую реализацию использовать. Может кто-нибудь объяснить, в чем разница?

Кроме того, что такое «дополнительная политика справедливости» в ArrayBlockingQueue?


person Community    schedule 19.08.2009    source источник
comment
Вы также забыли спросить о PriorityBlockingQueue, который полезен для указания порядка обработки потоков.   -  person IgorGanapolsky    schedule 21.04.2014


Ответы (6)


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

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

Наиболее важное различие между LinkedBlockingQueue и ConcurrentLinkedQueue заключается в том, что если вы запрашиваете элемент из LinkedBlockingQueue, а очередь пуста, ваш поток будет ждать, пока там что-то не будет. ConcurrentLinkedQueue сразу же вернется с поведением пустой очереди.

Какой из них зависит от того, нужна ли вам блокировка. Когда у вас много производителей и один потребитель, это звучит так. С другой стороны, если у вас много потребителей и только один производитель, вам может не понадобиться блокирующее поведение, и вы можете просто попросить потребителей проверить, пуста ли очередь, и продолжить, если это так.

person Community    schedule 19.08.2009
comment
Ответ вводит в заблуждение. И LinkedBlockingQueue, и ConcurrentLinkedQueue имеют метод poll (), который удаляет заголовок очереди или возвращает значение null (не блокирует), и предложение метода (E e), которое вставляется в конец очереди и не блокируется. Разница в том, что только LinkedBlockingQueue имеет блокирующие операции в дополнение к неблокирующим операциям - и за эту привилегию вы платите цену, которую LinkedBlockingQueue фактически имеет некоторую блокировку. Другой ответ объясняет это. - person Nakedible; 07.05.2011

ConcurrentLinkedQueue означает, что блокировки не выполняются ( т.е. нет синхронизированного (этого) или Lock.lock звонков). Во время модификаций он будет использовать операцию CAS - Сравнить и поменять местами, чтобы увидеть, Узел / tail остается таким же, как и при запуске. Если это так, операция завершается успешно. Если узел голова / хвост отличается, он будет вращаться и повторить попытку.

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

Справедливость означает, что реализация Lock будет поддерживать порядок потоков. Это означает, что если поток A входит, а затем входит поток B, поток A первым получит блокировку. Без всякой справедливости, на самом деле не определено, что происходит. Скорее всего, это будет следующий запланированный поток.

Что касается того, какой из них использовать, это зависит от обстоятельств. Я обычно использую ConcurrentLinkedQueue, потому что Время, необходимое моим продюсерам, чтобы поставить работу в очередь, разнообразно. У меня не так много продюсеров, которые работают одновременно. Но со стороны потребителя сложнее, потому что опрос не переходит в хорошее состояние сна. Вы должны сами справиться с этим.

person Community    schedule 19.08.2009
comment
И при каких условиях ArrayBlockingQueue лучше LinkedBlockingQueue? - person kolobok; 16.09.2012
comment
@akapelko ArrayBlockingQueue позволяет более детально упорядочивать. - person IgorGanapolsky; 22.04.2014
comment
Что это значит - он будет крутиться и пробовать снова. ? - person Lester; 25.08.2016

В заголовке вашего вопроса упоминаются очереди с блокировкой. Однако ConcurrentLinkedQueue не очередь блокировки.

BlockingQueue - это ArrayBlockingQueue, DelayQueue , LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueue и SynchronousQueue.

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

Поскольку ArrayBlockingQueue полезен только в том случае, если вы хотите ограничить количество элементов, я бы придерживался LinkedBlockingQueue.

person Community    schedule 19.08.2009
comment
Я удалил слово "Блокировка" из заголовка, спасибо. Позвольте мне посмотреть, понял ли я, то, что вы сказали, означает, что LinkedBlockingQueue может использоваться в сценариях нескольких потребителей / производителей для одного и того же объекта? - person David Hofmann; 19.08.2009
comment
Я думал, что ArrayBlockingQueue позволяет более детально упорядочивать потоки? Отсюда его преимущество. - person IgorGanapolsky; 22.04.2014

ArrayBlockingQueue имеет меньший объем памяти, он может повторно использовать узел элемента, в отличие от LinkedBlockingQueue, который должен создавать объект LinkedBlockingQueue $ Node для каждой новой вставки.

person Community    schedule 31.01.2013
comment
хорошая точка зрения! я предпочитаю ArrayBlockingQueue больше, чем LinkedBlockingQueue - person trillions; 25.03.2014
comment
Это не обязательно так - если ваша очередь большую часть времени близка к пустой, но должна иметь возможность увеличиваться, ArrayBlockingQueue будет иметь гораздо худший объем памяти - у него все еще есть большой массив, выделенный в памяти все время, в то время как LinkedBlockingQueue будет иметь незначительный объем памяти, когда он близок к пустому. - person Krease; 09.08.2014

  1. SynchronousQueue (Взято из другого вопроса)

SynchronousQueue - это скорее передача обслуживания, тогда как LinkedBlockingQueue разрешает только один элемент. Разница в том, что вызов put() к SynchronousQueue не вернется, пока не будет соответствующий вызов take(), но с LinkedBlockingQueue размером 1 вызов put() (в пустую очередь) вернется немедленно. По сути, это реализация BlockingQueue для случаев, когда вам действительно не нужна очередь (вы не хотите поддерживать какие-либо ожидающие данные).

  1. LinkedBlockingQueue (LinkedList Реализация, но не совсем JDK Реализация LinkedList Он использует статический внутренний класс Node для поддержания связей между элементами)

Конструктор для 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 (реализация массива)

Конструктор для 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 очевидна из конструктора, у которого есть базовая структура данных Array и другой связанный список.

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

person Community    schedule 12.12.2013

ConcurrentLinkedQueue не блокируется, а LinkedBlockingQueue - нет. Каждый раз, когда вы вызываете LinkedBlockingQueue.put () или LinkedBlockingQueue.take (), вам нужно сначала получить блокировку. Другими словами, у LinkedBlockingQueue плохой параллелизм. Если вам важна производительность, попробуйте ConcurrentLinkedQueue + LockSupport.

person Community    schedule 14.05.2015