LinkedBlockingQueue против ConcurrentLinkedQueue


112

Мой вопрос относится к заданному ранее вопросу . В ситуациях, когда я использую очередь для связи между потоками производителя и потребителя, люди обычно рекомендуют использовать LinkedBlockingQueueили ConcurrentLinkedQueue?

Каковы преимущества / недостатки использования одного над другим?

Основное отличие, которое я вижу с точки зрения API, заключается в том, что a LinkedBlockingQueueможет быть дополнительно ограничен.

Ответы:


110

Для потока производителя / потребителя я не уверен, что ConcurrentLinkedQueueэто даже разумный вариант - он не реализует BlockingQueue, что является фундаментальным интерфейсом для очередей производителей / потребителей IMO. Вам придется позвонить poll(), немного подождать, если вы ничего не нашли, а затем снова опросить и т. Д., Что приведет к задержкам, когда приходит новый элемент, и неэффективности, когда он пуст (из-за ненужного пробуждения от сна) .

Из документов для BlockingQueue:

BlockingQueue реализации предназначены для использования в первую очередь для очередей производитель-потребитель

Я знаю, что это не строго говорит о том, что для очередей производитель-потребитель следует использовать только очереди блокировки, но даже в этом случае ...


4
Спасибо, Джон - я этого не заметил. Итак, где / зачем вам использовать ConcurrentLinkedQueue?
Adamski

27
Когда вам нужно получить доступ к очереди из большого количества потоков, но вам не нужно «ждать» этого.
Джон Скит,

2
A ConcurrentLinkedQueueтакже полезен, если ваш поток проверяет несколько очередей. Например, на мультитенантном сервере. Предполагая, что по причинам изоляции вы не используете вместо этого одну блокирующую очередь и дискриминатор клиента.
LateralFractal

ваш случай справедлив только в том случае, если мы используем ограниченную очередь в неограниченной очереди take()и put()просто потребляем дополнительный ресурс (в смысле синхронизации), чем ConcurrentLinkedQueue . хотя это случай использования ограниченных очередей для сценариев производитель-потребитель
амарнатх хариш

@Adamski IMO, ConcurrentLinkedQueue - это просто связанный список для использования в многопоточной среде. Лучшей аналогией для этого были бы ConcurrentHashMap и HashMap.
Nishit

69

Этот вопрос заслуживает лучшего ответа.

Java ConcurrentLinkedQueueоснован на известном алгоритме, разработанном Магедом М. Майклом и Майклом Л. Скоттом для неблокирующей и свободной от блокировки очередей.

«Неблокирующий» здесь термин для конкурирующего ресурса (наша очередь) означает, что независимо от того, что делает планировщик платформы, например, прерывает поток, или если рассматриваемый поток просто слишком медленный, другие потоки конкурируют за тот же ресурс все еще сможет прогрессировать. Если, например, задействована блокировка, поток, удерживающий блокировку, может быть прерван, и все потоки, ожидающие этой блокировки, будут заблокированы. Внутренние блокировки ( synchronizedключевое слово) в Java также могут иметь серьезные последствия для производительности - например, при смещенной блокировке.вовлечен, и у вас действительно есть конкуренция, или после того, как виртуальная машина решит «раздуть» блокировку после периода отсрочки вращения и заблокировать конкурирующие потоки ... вот почему во многих контекстах (сценарии низкой / средней конкуренции) выполняется сравнение и -наборы по атомарным ссылкам могут быть гораздо более эффективными, и это именно то, что делают многие неблокирующие структуры данных.

Java ConcurrentLinkedQueueне только не блокирует, но и обладает замечательным свойством, которое производитель не борется с потребителем. В сценарии с одним производителем и одним потребителем (SPSC) это действительно означает, что не будет никаких разногласий. В сценарии с несколькими производителями и одним потребителем потребитель не будет бороться с производителями. Эта очередь действительно имеет конкуренцию, когда несколько производителей пытаются сделать это offer(), но это по определению параллелизм. Это в основном универсальная и эффективная неблокирующая очередь.

Что касается того, что это не ... BlockingQueueну, блокирование потока для ожидания в очереди - это ужасно ужасный способ проектирования параллельных систем. Не. Если вы не можете понять, как использовать a ConcurrentLinkedQueueв сценарии потребителя / производителя, просто переключитесь на абстракции более высокого уровня, например, на хорошую структуру акторов.


8
В последнем абзаце, почему вы говорите, что ожидание в очереди - ужасный способ проектирования параллельных систем? Если у нас есть группа потоков с 10 потоками, потребляющими задачи из очереди задач, что плохого в блокировке, когда в очереди задач меньше 10 задач?
Pacerier

11
@AlexandruNedelcu Вы не можете сделать размашистое заявление вроде «до безумия ужасно», когда очень часто сами актерские фреймворки, которые вы говорите, используют пулы потоков, которые сами вы используете BlockingQueue . Если вам нужна высокореактивная система и вы знаете, как бороться с противодавлением (что-то, что смягчает блокирующие очереди), то неблокирование явно лучше. Но ... часто блокирующий ввод-вывод и блокирующие очереди могут не выполнять неблокирующую работу, особенно если у вас есть длительные задачи, которые связаны с вводом-выводом и не могут быть разделены на n '.
Адам Гент,

1
@AdamGent - Фреймворки акторов действительно имеют реализацию почтовых ящиков на основе блокирующих очередей, но, на мой взгляд, это ошибка, потому что блокировка не работает через асинхронные границы и, таким образом, работает только в демонстрациях. Для меня это было источником разочарования, поскольку, например, идея Akka о работе с переполнением заключается в блокировании, а не удалении сообщений, до версии 2.4, которая еще не вышла. Тем не менее, я не верю, что есть варианты использования, в которых очереди с блокировкой могут быть лучше. Вы также объединяете две вещи, которые не следует объединять. Я не говорил о блокировке ввода-вывода.
Александру Неделку

1
@AlexandruNedelcu, хотя я в целом согласен с вами по поводу противодавления, мне еще предстоит увидеть "безблокировочную" систему сверху вниз. Где-то в стеке технологий, будь то Node.js, Erlang, Golang, он использует какую-то стратегию ожидания, будь то очередь блокировки (блокировки) или CAS, вращающая свою блокировку, а в некоторых случаях традиционная стратегия блокировки работает быстрее. Очень сложно не иметь блокировок из-за согласованности, и это особенно важно с блокирующими io и планировщиками, которые являются ~ Producer / Consumer. ForkJoinPool работает с краткосрочными задачами, и у него все еще есть вращающиеся блокировки CAS.
Адам Гент,

1
@AlexandruNedelcu Я думаю, если вы можете показать мне, как вы можете использовать ConcurrentLinkedQueue (который, кстати, не ограничен, следовательно, мой слабый аргумент противодавления) для шаблона Producer / Consumer, который является шаблоном, необходимым для планировщиков и пулов потоков, я думаю, я уступлю и признаю, что BlockingQueue никогда не должен использоваться (и вы не можете обманывать и делегировать что-либо другое, выполняющее планирование, например, akka, поскольку это, в свою очередь, будет выполнять блокировку / ожидание, поскольку это производитель / потребитель).
Адам Гент,

33

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

ConcurrentLinkedQueueне использует блокировки, а использует CAS для операций ввода / вывода, что потенциально снижает конкуренцию со многими потоками производителей и потребителей. Но будучи структурой данных «без ожидания», ConcurrentLinkedQueueона не будет блокироваться, когда она пуста, а это означает, что потребителю придется иметь дело с take()возвращаемыми nullзначениями путем «ожидания занятости», например, когда поток потребителя поглощает процессор.

Итак, какой из них «лучше», зависит от количества потоков-потребителей, от скорости их потребления / производства и т. Д. Для каждого сценария требуется эталонный тест.

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


одно сомнение здесь. Как вы упомянули, потребитель ждет, когда очередь пуста ... как долго он ждет. Кто его уведомит, чтобы не ждать?
Бринал

@brindal Я знаю, что единственный способ подождать - это зациклить. Это серьезная проблема, которой здесь не уделялось много внимания. Простое выполнение цикла ожидания данных требует много процессорного времени. Вы поймете это, когда ваши поклонники начнут набирать обороты. Единственное средство - усыпить петлю. Так что это проблема в системе с несогласованным потоком данных. Возможно, я неправильно понимаю ответ Александру Неделку, но операционная система сама по себе является параллельной системой, которая была бы чрезвычайно неэффективной, если бы она была заполнена неблокирующими циклами событий.
orodbhen

хорошо, но если он unbounded blockingqueueбудет использоваться, будет ли он лучше, чем параллельный ConcurrentLinkedQueue
протокол на

@orodbhen Засыпание также не устраняет потери. ОС должна проделать большую работу, чтобы вывести поток из спящего режима, запланировать и запустить его. Если сообщения еще недоступны, эта работа, выполняемая вашей ОС, тратится зря. Я бы порекомендовал лучше использовать BlockingQueue, так как он был специально разработан для проблемы производитель-потребитель.
Nishit

на самом деле, меня очень интересует часть «скорость потребления / производства», так что же лучше, если скорость будет высокой?
workplaylifecycle

0

Другое решение (которое плохо масштабируется) - это каналы рандеву: java.util.concurrent SynchronousQueue


0

Если ваша очередь не расширяется и содержит только один поток производителя / потребителя. Вы можете использовать очередь без блокировки (вам не нужно блокировать доступ к данным).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.