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. 類似点

LinkedBlockingQueueConcurrentLinkedQueueはどちらもキューの実装であり、いくつかの共通の特性を共有しています。 これら2つのキューの類似点について説明しましょう。

  1. 両方はキューインターフェイスを実装します
  2. どちらもリンクされたノードを使用して要素を格納します
  3. 両方のは同時アクセスシナリオに適しています

5. 違い

これらのキューには両方とも特定の類似点がありますが、特性にも大きな違いがあります。

特徴 LinkedBlockingQueue ConcurrentLinkedQueue
自然をブロックする これはブロッキングキューであり、BlockingQueueインターフェイスを実装します これは非ブロッキングキューであり、 BlockingQueueインターフェイスを実装していません。
キューサイズ これはオプションで制限されたキューです。つまり、作成中にキューサイズを定義するためのプロビジョニングがあります。 これは無制限のキューであり、作成時にキューサイズを指定するためのプロビジョニングはありません
自然をロックする それはロックベースのキューです ロックフリーキューです
アルゴリズム 2ロックキューアルゴリズムに基づくロックを実装します それはに依存しています非ブロッキング、ロックフリーキュー用のMichael&Scottアルゴリズム
実装 の中に 2ロックキューアルゴリズムメカニズム、 LinkedBlockingQueue 2つの異なるロックを使用します– putLock そしてその takeLock 。 The 置く/取る操作は最初のロックタイプを使用し、 取る/投票する操作は他のロックタイプを使用します 操作にCAS(Compare-And-Swap )を使用します
ブロッキング動作 ブロッキングキューです。 したがって、キューが空の場合、アクセスしているスレッドをブロックします キューが空の場合、アクセスしているスレッドをブロックせず、nullを返します。

6. 結論

この記事では、LinkedBlockingQueueConcurrentLinkedQueueについて学びました。

最初に、これら2つのキューの実装とそれらの特性のいくつかについて個別に説明しました。 次に、これら2つのキューの実装の類似点を確認しました。 最後に、これら2つのキューの実装の違いを調べました。

いつものように、例のソースコードはGitHubから入手できます。