LinkedBlockingQueueとConcurrentLinkedQueue
1. 序章
LinkedBlockingQueueとConcurrentLinkedQueueは、Javaで最も頻繁に使用される2つの同時キューです。 両方のキューは同時データ構造として使用されることがよくありますが、それらの間には微妙な特性と動作の違いがあります。
この短いチュートリアルでは、これらのキューの両方について説明し、それらの類似点と相違点について説明します。
2. LinkedBlockingQueue
LinkedBlockingQueue は、オプションで制限されたブロッキングキューの実装です。つまり、は、必要に応じてキューサイズを指定できることを意味します。
最大100個の要素を含むことができるLinkedBlockingQueueを作成しましょう。
BlockingQueue<Integer> boundedQueue = new LinkedBlockingQueue<>(100);
サイズを指定しないだけで、無制限のLinkedBlockingQueueを作成することもできます。
BlockingQueue<Integer> unboundedQueue = new LinkedBlockingQueue<>();
無制限のキューは、作成中にキューのサイズが指定されていないことを意味します。 したがって、要素がキューに追加されると、キューは動的に大きくなる可能性があります。 ただし、メモリが残っていない場合、キューはjava.lang.OutOfMemoryError。をスローします。
既存のコレクションからLinkedBlockingQueueを作成することもできます。
Collection<Integer> listOfNumbers = Arrays.asList(1,2,3,4,5);
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(listOfNumbers);
LinkedBlockingQueue クラスは、BlockingQueueインターフェイスを実装し、ブロッキングの性質を提供します。
ブロッキングキューは、キューがいっぱいになった場合(キューが制限されている場合)または空になった場合に、アクセスしているスレッドをブロックすることを示します。 キューがいっぱいの場合、新しい要素に使用できるスペースがない限り、新しい要素を追加するとアクセススレッドがブロックされます。 同様に、キューが空の場合、要素にアクセスすると呼び出し元のスレッドがブロックされます。
ExecutorService executorService = Executors.newFixedThreadPool(1);
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
executorService.submit(() -> {
try {
queue.take();
}
catch (InterruptedException e) {
// exception handling
}
});
上記のコードスニペットでは、空のキューにアクセスしています。 したがって、 take メソッドは、呼び出し元のスレッドをブロックします。
LinkedBlockingQueue のブロッキング機能には、ある程度のコストが伴います。 このコストは、すべてのputまたはtake操作が、プロデューサースレッドまたはコンシューマースレッド間でロック競合されるためです。 したがって、多くのプロデューサーとコンシューマーが存在するシナリオでは、putとアクションの実行が遅くなる可能性があります。
3. ConcurrentLinkedQueue
A ConcurrentLinkedQueue
空のConcurrentLinkedQueueを作成しましょう。
ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
既存のコレクションからConcurrentLinkedQueueを作成することもできます。
Collection<Integer> listOfNumbers = Arrays.asList(1,2,3,4,5);
ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>(listOfNumbers);
LinkedBlockingQueueとは異なり、ConcurrentLinkedQueueは非ブロッキングキューです。 したがって、キューが空になると、スレッドはブロックされません。 代わりに、nullを返します。 制限がないため、新しい要素を追加するための追加のメモリがない場合は、 java.lang.OutOfMemoryErrorがスローされます。
非ブロッキングであることに加えて、ConcurrentLinkedQueueには追加機能があります。
生産者/消費者シナリオでは、消費者は生産者と争うことはありません。 ただし、複数のプロデューサーが互いに競合します。
int element = 1;
ExecutorService executorService = Executors.newFixedThreadPool(2);
ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
Runnable offerTask = () -> queue.offer(element);
Callable<Integer> pollTask = () -> {
while (queue.peek() != null) {
return queue.poll().intValue();
}
return null;
};
executorService.submit(offerTask);
Future<Integer> returnedElement = executorService.submit(pollTask);
assertThat(returnedElement.get().intValue(), is(equalTo(element)));
最初のタスクofferTaskはキューに要素を追加し、2番目のタスクpollTaskはキューから要素を取得します。 ConcurrentLinkedQueueは非ブロッキングであり、null値を返す可能性があるため、ポーリングタスクはさらに最初に要素のキューをチェックします。
4. 類似点
LinkedBlockingQueueとConcurrentLinkedQueueはどちらもキューの実装であり、いくつかの共通の特性を共有しています。 これら2つのキューの類似点について説明しましょう。
- 両方はキューインターフェイスを実装します
- どちらもリンクされたノードを使用して要素を格納します
- 両方のは同時アクセスシナリオに適しています
5. 違い
これらのキューには両方とも特定の類似点がありますが、特性にも大きな違いがあります。
特徴 | LinkedBlockingQueue | ConcurrentLinkedQueue |
---|---|---|
自然をブロックする | これはブロッキングキューであり、BlockingQueueインターフェイスを実装します | これは非ブロッキングキューであり、 BlockingQueueインターフェイスを実装していません。 |
キューサイズ | これはオプションで制限されたキューです。つまり、作成中にキューサイズを定義するためのプロビジョニングがあります。 | これは無制限のキューであり、作成時にキューサイズを指定するためのプロビジョニングはありません |
自然をロックする | それはロックベースのキューです | ロックフリーキューです |
アルゴリズム | 2ロックキューアルゴリズムに基づくロックを実装します | それはに依存しています非ブロッキング、ロックフリーキュー用のMichael&Scottアルゴリズム |
実装 | の中に 2ロックキューアルゴリズムメカニズム、 LinkedBlockingQueue 2つの異なるロックを使用します– putLock そしてその takeLock |
操作にCAS(Compare-And-Swap )を使用します |
ブロッキング動作 | ブロッキングキューです。 したがって、キューが空の場合、アクセスしているスレッドをブロックします | キューが空の場合、アクセスしているスレッドをブロックせず、nullを返します。 |
6. 結論
この記事では、LinkedBlockingQueueとConcurrentLinkedQueueについて学びました。
最初に、これら2つのキューの実装とそれらの特性のいくつかについて個別に説明しました。 次に、これら2つのキューの実装の類似点を確認しました。 最後に、これら2つのキューの実装の違いを調べました。
いつものように、例のソースコードはGitHubでから入手できます。