web-dev-qa-db-ja.com

Javaではどの同時キュー実装を使用する必要がありますか?

JavaDocsから:

  • ConcurrentLinkedQueue は、多くのスレッドが共通のコレクションへのアクセスを共有する場合に適切な選択です。このキューはnull要素を許可しません。
  • ArrayBlockingQueue は、古典的な「境界付きバッファ」であり、固定サイズの配列には、プロデューサによって挿入され、コンシューマによって抽出された要素が保持されます。このクラスは、待機中のプロデューサスレッドとコンシューマスレッドを順序付けるためのオプションの公平性ポリシーをサポートします
  • LinkedBlockingQueue 通常、アレイベースのキューよりもスループットが高くなりますが、ほとんどの同時実行アプリケーションで予測可能なパフォーマンスは低くなります。

2つのシナリオがあり、1つは1つのコンシューマーで多くのプロデューサー(それを使用するスレッド)をサポートするためにキューを必要とし、もう1つは他の方法です。

使用する実装がわかりません。誰かが違いを説明できますか?

また、ArrayBlockingQueueの「オプションの公平性ポリシー」とは何ですか?

122
David Hofmann

基本的に、それらの違いはパフォーマンス特性とブロック動作です。

最も簡単なものから順に、ArrayBlockingQueueは固定サイズのキューです。したがって、サイズを10に設定して11番目の要素を挿入しようとすると、別のスレッドが要素を削除するまでinsertステートメントがブロックされます。公平性の問題は、複数のスレッドが同時に挿入と削除を試みた場合(つまり、キューがブロックされた期間中)に発生することです。公平性アルゴリズムにより、最初に要求されるスレッドが最初に取得されるスレッドになります。そうしないと、特定のスレッドが他のスレッドよりも長く待機し、予測できない動作を引き起こす可能性があります(後で開始した他のスレッドが最初に処理されたため、1つのスレッドが数秒かかることがあります)。トレードオフは、公平性の管理にオーバーヘッドがかかり、スループットが低下することです。

LinkedBlockingQueueConcurrentLinkedQueueの最も重要な違いは、LinkedBlockingQueueから要素を要求し、キューが空の場合、スレッドは何かがあるまで待機することです。 ConcurrentLinkedQueueは、空のキューの動作ですぐに戻ります。

あなたはブロッキングが必要かどうかに依存します。多くのプロデューサーと1つのコンシューマーがある場合、そのように聞こえます。一方、多くのコンシューマーと1つのプロデューサーのみが存在する場合は、ブロッキング動作が不要な場合があり、キューが空かどうかをコンシューマーに確認させ、空いている場合は先に進んでもらうことができます。

47
Yishai

ConcurrentLinkedQueue は、ロックが取得されないことを意味します(すなわち、synchronized(this)または Lock.lock 呼び出しはありません)。変更中に CAS-比較とスワップ 操作を使用して、ヘッド/テールノードが開始時と同じかどうかを確認します。その場合、操作は成功します。頭部/尾部のノードが異なる場合、ノードは回転して再試行します。

LinkedBlockingQueue は、変更する前にロックを取得します。したがって、オファーの呼び出しは、ロックを取得するまでブロックされます。 TimeUnitを使用するオファーオーバーロードを使用して、追加を放棄する前にX時間だけ待つことを表明できます(通常、Xミリ秒後にメッセージが古くなるメッセージタイプのキューに適しています)。

公平性とは、Lock実装がスレッドの順序を維持することを意味します。つまり、スレッドAが入ってからスレッドBが入った場合、スレッドAは最初にロックを取得します。公平性がなければ、実際に何が起こるかは未定義です。ほとんどの場合、スケジュールされるのは次のスレッドです。

どちらを使用するかは、それによって異なります。私は ConcurrentLinkedQueue を使用する傾向があります。なぜなら、プロデューサーが作業をキューに入れるのにかかる時間はさまざまだからです。まったく同じ瞬間にプロデュースするプロデューサーはあまりいません。しかし、アンケートはナイススリープ状態にならないため、消費者側はより複雑です。それを自分で処理する必要があります。

109
Justin Rudd

質問のタイトルには、ブロッキングキューが記載されています。ただし、ConcurrentLinkedQueuenotブロッキングキューです。

BlockingQueue sは、ArrayBlockingQueueDelayQueueLinkedBlockingDequeLinkedBlockingQueuePriorityBlockingQueue、およびSynchronousQueueです。

これらのいくつかは明らかに目的に適合しません(DelayQueuePriorityBlockingQueue、およびSynchronousQueue)。 LinkedBlockingQueueLinkedBlockingDequeは、後者が両端キュー(Dequeインターフェイスを実装)であることを除いて同一です。

ArrayBlockingQueueは、要素の数を制限したい場合にのみ有用なので、LinkedBlockingQueueに固執します。

8
Powerlord

ArrayBlockingQueueのメモリフットプリントは小さく、要素ノードを再利用できます。新しい挿入ごとにLinkedBlockingQueue $ Nodeオブジェクトを作成する必要があるLinkedBlockingQueueとは異なります。

4
Deng
  1. SynchronousQueue(別の 質問 から取得)

SynchronousQueueはハンドオフのようなものですが、LinkedBlockingQueueは単一の要素のみを許可します。違いは、SynchronousQueueへのput()呼び出しは、対応するtake()呼び出しがあるまで戻りませんが、サイズ1のLinkedBlockingQueueを使用すると、put()呼び出し(空のキューへ)がすぐに戻ります。本質的には、キューが本当に必要ない場合(保留中のデータを保持したくない場合)のBlockingQueue実装です。

  1. LinkedBlockingQueueLinkedList実装ですが、LinkedListのJDK実装ではありません。静的内部クラスNodeを使用して要素間のリンクを維持します)

LinkedBlockingQueueのコンストラクター

public LinkedBlockingQueue(int capacity) 
{
        if (capacity < = 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node< E >(null);   // Maintains a underlying linkedlist. ( Use when size is not known )
}

リンクを維持するために使用されるノードクラス

static class Node<E> {
    E item;
    Node<E> next;
    Node(E x) { item = x; }
}

3。 ArrayBlockingQueue(配列の実装)

ArrayBlockingQueueのコンストラクター

public ArrayBlockingQueue(int capacity, boolean fair) 
{
            if (capacity < = 0)
                throw new IllegalArgumentException();
            this.items = new Object[capacity]; // Maintains a underlying array
            lock = new ReentrantLock(fair);
            notEmpty = lock.newCondition();
            notFull =  lock.newCondition();
}

IMHOのArrayBlockingQueueLinkedBlockingQueueの最大の違いは、コンストラクターから明らかです基になるデータ構造配列と他のlinkedList

ArrayBlockingQueuesingle-lock double condition algorithm を使用し、LinkedBlockingQueueは「2ロックキュー」アルゴリズムのバリアントであり、2つのロック2の条件(takeLock、putLock)があります

1
Vipin

ConcurrentLinkedQueueはロックフリーですが、LinkedBlockingQueueはそうではありません。 LinkedBlockingQueue.put()またはLinkedBlockingQueue.take()を呼び出すたびに、最初にロックを取得する必要があります。つまり、LinkedBlockingQueueの同時実行性は不十分です。パフォーマンスを重視する場合は、ConcurrentLinkedQueue + LockSupportを試してください。

0
user319609