LinkedBlockingQueueとConcurrentLinkedQueue


112

私の質問は、以前に尋ねたこの質問に関連しいます。プロデューサースレッドとコンシューマースレッド間の通信にキューを使用している状況では、一般的に人々は、LinkedBlockingQueueまたはConcurrentLinkedQueue

どちらを使用する場合の利点/欠点は何ですか?

APIの観点から見ることができる主な違いは、LinkedBlockingQueueオプションで境界を設定できることです。

回答:


110

プロデューサー/コンシューマースレッドの場合、ConcurrentLinkedQueueそれが妥当なオプションであるかどうかはわかりません。実装されていませんBlockingQueue。これは、プロデューサー/コンシューマーキューIMOの基本的なインターフェイスです。を呼び出しpoll()、何も見つからなかった場合は少し待ってから、もう一度ポーリングする必要があります...新しいアイテムが入ってくると遅延が発生し、空の場合は非効率になります(スリープから不必要に目覚めるため)。 。

BlockingQueueのドキュメントから:

BlockingQueue 実装は、主にプロデューサー-コンシューマーキューに使用するように設計されています

私は、プロデューサー/コンシューマーキューにブロッキングキューのみを使用する必要があるとは厳密に言っていませんが、それでも...


4
ありがとうジョン-私はそれに気づかなかった。では、どこで/なぜConcurrentLinkedQueueを使用するのでしょうか。
アダムスキー

27
多数のスレッドからキューにアクセスする必要があるが、「待機」する必要がない場合。
ジョンスキート

2
A ConcurrentLinkedQueueは、スレッドが複数のキューをチェックしている場合にも役立ちます。たとえば、マルチテナントサーバーです。分離の理由で、代わりに単一のブロッキングキューとテナント識別子を使用しないと仮定します。
LateralFractal 2016年

あなたのケースでは、我々が使用している場合にのみ当てはまる有界キューの中で、無限のキューtake()put()単によりも余分なリソース(同期のinterms)を消費しますConcurrentLinkedQueue 。のための有界キューを使用する場合であるが、生産者-消費者のシナリオ
amarnathハリッシュ

@Adamski IMO、ConcurrentLinkedQueueは、マルチスレッド環境で使用される単なるリンクリストです。これの最もよい類似は、ConcurrentHashMapとHashMapです。
Nishit

69

この質問はより良い答えに値します。

Java ConcurrentLinkedQueue、Maged M. MichaelとMichael L. Scottによるブロッキングのないロックフリーキュー用の有名なアルゴリズムに基づいています。

ここで競合するリソース(キュー)の用語としての「非ブロッキング」とは、スレッドの中断などのプラットフォームのスケジューラの動作に関係なく、または問題のスレッドが単に遅すぎる場合、他のスレッドが同じリソースについて競合していることを意味しますまだ進行できます。たとえば、ロックが関係している場合、ロックを保持しているスレッドが中断され、そのロックを待機しているすべてのスレッドがブロックされる可能性があります。synchronizedJavaの組み込みロック(キーワード)も、バイアスされたロックのように、パフォーマンスに重大なペナルティをもたらす可能性がありますが関与していて、競合が発生した場合、またはVMがスピン猶予期間後にロックを「膨らませ」、競合するスレッドをブロックすることを決定した後...多くのコンテキスト(低/中競合のシナリオ)で比較し、アトミック参照の-setsははるかに効率的であり、これはまさに多くの非ブロッキングデータ構造が行っていることです。

Java ConcurrentLinkedQueueは非ブロッキングであるだけでなく、プロデューサーがコンシューマーと競合しないという素晴らしい特性を持っています。単一のプロデューサー/シングルコンシューマーシナリオ(SPSC)では、これは実際に話す必要のある競合がないことを意味します。複数のプロデューサー/シングルコンシューマーのシナリオでは、コンシューマーはプロデューサーと競合しません。このキューは、複数のプロデューサーがを試みたときに競合しますが、それは本質的にoffer()同時実行性です。基本的には、汎用で効率的な非ブロッキングキューです。

それがでないことに関してはBlockingQueue、スレッドをキューで待機するのをブロックすることは、並行システムを設計するためにひどく恐ろしい方法です。しないでください。ConcurrentLinkedQueueコンシューマー/プロデューサーのシナリオでを使用する方法がわからない場合は、優れたアクターフレームワークのように、より高いレベルの抽象化に切り替えるだけです。


8
あなたの最後の段落について、なぜキューで待機することが並行システムを設計するひどい方法であると言うのですか?タスクキューからタスクを食べるスレッドが10個あるスレッドグループがある場合、タスクキューのタスクが10個未満の場合にブロックするとどうなりますか?
Pacerier 2014

11
@AlexandruNedelcu「奇妙なほどひどい」のような抜本的なステートメントを作成することはできません。使用するアクターフレームワークが、自分自身がBlockingQueueであるスレッドプールを使用することがよくあります。非常にリアクティブなシステムが必要で、バックプレッシャー(ブロッキングキューが緩和するもの)に対処する方法を知っている場合は、ノンブロッキングよりも明らかに優れています。ただし、特に、IOにバインドされていて分割して征服できない長期実行タスクがある場合は特に、ブロッキングIOとブロッキングキューが非ブロッキングを実行することがあります。
アダムゲント2015

1
@AdamGent-アクターフレームワークにはブロッキングキューに基づくメールボックスの実装がありますが、ブロッキングは非同期の境界を越えて機能しないため、デモでのみ機能するため、私の意見ではバグです。私にとってこれは欲求不満の原因でした。たとえば、オーバーフローを処理するというAkkaの概念は、バージョン2.4になるまでメッセージをドロップするのではなく、ブロックすることです。そうは言っても、ブロッキングキューの方が優れているユースケースがあるとは思いません。また、融合してはならない2つのことも融合します。I / Oのブロックについては話していません。
Alexandru Nedelcu

1
@AlexandruNedelcuバックプレッシャーについては一般的にあなたに同意しますが、上から下に「ロックフリー」システムはまだ見ていません。Node.js、Erlang、Golangなどのテクノロジースタックのどこかで、それがブロッキングキュー(ロック)であるか、ブロッキングをスピンしているCASであるかを問わず、ある種の待機戦略を使用しており、場合によっては従来のロック戦略の方が高速です。一貫性のためにロックを保持することは非常に困難です。これは、プロデューサー/コンシューマーであるioとスケジューラーをブロックする場合に特に重要です。ForkJoinPoolは実行時間の短いタスクで動作し、CASスピンロックがまだあります。
アダム・ゲント

1
@AlexandruNedelcuスケジューラとスレッドプーリングに必要なパターンであるプロデューサ/コンシューマパターンにConcurrentLinkedQueue(境界がないため、弱いバックプレッシャ引数)を使用する方法を示すことができるかどうかを私は示すと思います。 BlockingQueueは絶対に使用しないでください(また、プロデューサ/コンシューマであるため、ブロッキング/待機を実行するため、スケジューリングを実行する他の何か、つまりakkaにチートおよびデリゲートすることはできません)。
アダム・ゲント

33

LinkedBlockingQueueキューが空または一杯で、それぞれのコンシューマ/プロデューサスレッドがスリープ状態になると、コンシューマまたはプロデューサをブロックします。ただし、このブロッキング機能にはコストが伴います。すべてのputまたはtake操作は、プロデューサーまたはコンシューマー(存在する場合)の間で競合するため、多くのプロデューサー/コンシューマーが存在するシナリオでは、操作が遅くなる可能性があります。

ConcurrentLinkedQueueはロックを使用していませんが、そのput / take操作でCASを使用しているため、多くのプロデューサスレッドとコンシューマスレッドとの競合が減少する可能性があります。ただし、「待機なし」のデータ構造であるConcurrentLinkedQueueため、空の場合はブロックされません。つまり、コンシューマースレッドはCPUを使い果たすなど、「ビジー待機」によってtake()戻りnull値を処理する必要があります。

したがって、どちらが「より良い」かは、コンシューマスレッドの数、それらが消費/生成する速度などに依存します。各シナリオにはベンチマークが必要です。

ConcurrentLinkedQueue明らかに優れている1つの特定の使用例は、プロデューサーが最初に何かを作成し、キューに作業を配置して、コンシューマーが消費を開始したのみ、ジョブが終了するときです。(ここでは、プロデューサーとコンシューマーの間には並行性はありませんが、プロデューサーとプロデューサーとコンシューマーの間には並行性があります)


ここで1つの疑問。あなたが述べたように、キューが空になるとコンシューマーは待機します。誰が待たないように通知しますか?
Brinal 2015

@brindal待機する唯一の方法は、私が知っているように、ループ内にあります。これは、ここでの回答ではあまり注目されていない重要な問題です。データを待つループを実行するだけでは、多くのプロセッサ時間が消費されます。ファンが回転し始めると、それがわかります。唯一の救済策は、ループをスリープ状態にすることです。したがって、データフローに一貫性がないシステムの問題です。おそらく私はAlexandruNedelcuの答えを誤解しているかもしれませんが、オペレーティングシステム自体は並行システムであり、非ブロッキングイベントループでいっぱいである場合、それは非常に非効率的です。
orodbhen 2017

大丈夫ですが、unbounded blockingqueue使用する場合はCASベースのコンカレントよりも優れていますConcurrentLinkedQueue
アマルナトは、18

@orodbhenスリープ状態にしても、無駄がなくなるわけではありません。OSは、スレッドをスリープ状態から解放してスケジュールし、実行するために多くの作業を行う必要があります。メッセージがまだ利用できない場合、OSによって行われたその作業は無駄になります。BlockingQueueはプロデューサー/コンシューマーの問題用に特別に設計されているため、使用することをお勧めします。
Nishit

実際、私は「消費/生産率」の部分に非常に興味があります。そのため、率が高くなる場合はどちらが良いですか?
workplaylifecycle


0

キューが拡張不可であり、プロデューサー/コンシューマースレッドが1つだけ含まれている場合。ロックレスキューを使用できます(データアクセスをロックする必要はありません)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.