LinkedBlockingQueue проти ConcurrentLinkedQueue


112

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

Які переваги / недоліки використання одного над іншим?

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

Відповіді:


110

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

З документів для BlockingQueue:

BlockingQueue реалізація розроблена таким чином, щоб використовувати їх насамперед для черг виробник-споживач

Я знаю, що це не чітко говорить про те, що для черг виробника-споживача слід використовувати лише блокуючі черги, але навіть так ...


4
Дякую Джону - я цього не помічав. То де / навіщо ви використовуєте ConcurrentLinkedQueue?
Адамський

27
Коли вам потрібно отримати доступ до черги з безлічі потоків, але не потрібно "чекати" її.
Джон Скіт

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

ваш випадок справджується лише в тому випадку, якщо ми використовуємо обмежену чергу, в необмеженій черзі take()і put()просто споживаємо додатковий ресурс (інтервали синхронізації), ніж ConcurrentLinkedQueue . хоча це стосується використання обмежених черг для сценаріїв «Продюсер-споживач»
amarnath збирається

@Adamski IMO, ConcurrentLinkedQueue - лише пов'язаний список для використання у багатопотоковому середовищі. Найкращою аналогією для цього були б ConcurrentHashMap та HashMap.
Нішит

69

Це питання заслуговує кращої відповіді.

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

"Неблокуючий" як термін тут для суперечливого ресурсу (нашої черги) означає, що незалежно від того, що робить планувальник платформи, як-от переривання потоку, або якщо нитка, про яку йдеться, просто занадто повільна, інші потоки змагаються за той самий ресурс все ще зможе прогресувати. Якщо, наприклад, задіяний замок, то нитка, що тримає замок, може бути перервана, і всі потоки, які чекають цього блокування, будуть заблоковані. Внутрішні блокування ( synchronizedключове слово) на Java також можуть суворо штрафувати за ефективність, як, наприклад, при необ'єктивному блокуваннізадіяний і у вас є суперечки, або після того, як VM вирішить "надути" замок після періоду граціозного відключення і блокувати нитки, що складаються ... саме тому у багатьох контекстах (сценарії низької / середньої суперечки) роблять порівняння та -набори атомних посилань можуть бути набагато ефективнішими, і саме це робить багато неблокуючих структур даних.

Java ConcurrentLinkedQueueне тільки не блокує, але й володіє приголомшливою властивістю, яку виробник не суперечить споживачеві. У сценарії одного виробника / єдиного споживача (SPSC) це дійсно означає, що говорити не доводиться. У сценарії декількох виробників / одного споживача споживач не буде протиставлятися виробникам. Ця черга має суперечки, коли намагаються зробити кілька виробників offer(), але це паралельність за визначенням. Це в основному загальна мета та ефективна неблокуюча черга.

Що стосується того, що це не бути BlockingQueueдобре, блокування нитки чекати в черзі - це дивно жахливий спосіб проектування сумісних систем. Не варто. Якщо ви не можете зрозуміти, як користуватися ConcurrentLinkedQueueсценарієм споживача / виробника, просто перейдіть до абстракцій вищого рівня, як у хороших акторських рамок.


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

11
@AlexandruNedelcu Ви не можете зробити розгорнуту заяву на зразок "дивовижно жахливої", коли дуже часто для самих акторських рамок ви говорите, щоб використовувати потокові пули, які самі ви BlockingQueue 's. Якщо вам потрібна високореактивна система, і ви знаєте, як боротися із зворотним тиском (те, що блокує черги пом'якшують), ніж неблокування, явно перевершує. Але .. часто блокування вводу-виводу та блокування черг може виконувати неблокуючі, особливо якщо у вас є тривалі завдання, які пов'язані з IO, і їх неможливо розділити.
Адам Гент

1
@AdamGent - Рамки акторів мають реалізацію поштових скриньок, заснованих на блокуванні черг, але, на мою думку, це помилка, оскільки блокування не працює над асинхронними межами і, таким чином, працює лише в демонстраціях. Для мене це стало причиною розладу, як, наприклад, поняття Акки щодо боротьби із переповненням - блокувати замість того, щоб відкидати повідомлення, до версії 2.4, тобто такої, яка ще не вийшла. Це сказав, що я не вірю, що є випадки використання, для яких блокування черг може бути вище. Ви також плутаєте дві речі, які не слід плутати. Я не говорив про блокування вводу-виводу.
Олександру Недельку

1
@AlexandruNedelcu, хоча я, як правило, погоджуюся з вами щодо тиску, я ще не бачив системи "без замка" зверху вниз. Десь у стеку технологій, чи то її Node.js, Erlang, Golang, вона використовує якусь стратегію очікування, чи це блокування (замок), або CAS, що здійснює блокування, а в деяких випадках традиційна стратегія блокування швидша. Дуже важко не мати замків через послідовність, і це особливо важливо при блокуванні іо та планувальників, які є ~ Виробник / Споживач. ForkJoinPool працює з короткими запущеними завданнями, і в ньому все ще є спінінг-блокування CAS.
Адам Гент

1
@AlexandruNedelcu Я думаю, якщо ви зможете показати мені, як ви можете використовувати ConcurrentLinkedQueue (яка не обмежена btw, отже, мій слабкий аргумент зворотного тиску) для шаблону виробника / споживача, що є моделлю, необхідним для планувальників і потокової передачі, я думаю, я поступлюсь і визнаю, що BlockingQueue's ніколи не слід використовувати (і ви не можете обманювати та делегувати щось інше, роблячи планування, тобто akka, оскільки це в свою чергу буде робити блокування / очікування, оскільки воно є виробником / споживачем).
Адам Гент

33

LinkedBlockingQueueблокує споживача або виробника, коли черга порожня або заповнена, і відповідна нитка споживача / виробника переходить у режим сну. Але ця функція блокування пов'язана з вартістю: кожна операція "поставити або взяти" є замком між виробниками чи споживачами (якщо їх багато), тому в сценаріях з багатьма виробниками / споживачами операція може бути повільнішою.

ConcurrentLinkedQueueне використовує замки, але CAS в операціях “поставити / взяти” потенційно зменшує суперечки з багатьма виробниками та споживачами. Але будучи структурою даних "чекати безкоштовно", ConcurrentLinkedQueueвона не блокується, коли вона порожня, це означає, що споживачеві доведеться мати справу з take()поверненими nullзначеннями "зайнятим очікуванням", наприклад, з споживчою потоком споживача CPU.

Отже, яка з них "краща" залежить від кількості споживчих ниток, від норми, яку вони споживають / виробляють тощо. Для кожного сценарію необхідний орієнтир.

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


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

@brindal Єдиний спосіб чекати, що я знаю, - це цикл. Що є суттєвою проблемою, на яку тут не було приділено багато уваги. Просто запуск циклу в очікуванні даних вимагає багато процесорного часу. Ви дізнаєтесь це, коли ваші шанувальники почнуть шанувати. Єдиний засіб - покласти сон у петлю. Тож це проблема в системі з непослідовним потоком даних. Можливо, я неправильно розумію відповідь ОлександруНедельку, але сама операційна система - це паралельна система, яка була б надзвичайно неефективною, якби вона була повна неблокуючих циклів подій.
orodbhen

Гаразд, але якщо unbounded blockingqueueвикористовується, було б краще, ніж одночасне використання на основі CASConcurrentLinkedQueue
amarnath збирається

@orodbhen Покладання сну також не усуне марнотратство. ОС має зробити багато роботи, щоб вивести нитку зі сну та розкладу та запустити її. Якщо повідомлень ще немає, ця робота, виконана вашою ОС, втрачається. Я б рекомендував краще використовувати BlockingQueue, оскільки він був спеціально розроблений для проблем виробника-споживача.
Нішит

насправді я дуже зацікавлений у частині "споживати / виробляти норму", тож яка з них краща, якщо ставка зростає?
workplaylifecycle


0

Якщо ваша черга не розгортається і містить лише одну нитку виробника / споживача. Ви можете користуватися безблоковою чергою (не потрібно блокувати доступ до даних).

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