13

リスト/テーブルのエントリをバックアップするために概念的に使用されるConcurrentLinkedQueueを読み書きするマルチスレッドアプリがあります。私はもともとこれにConcurrentHashMapを使用しましたが、これはうまく機能しました。注文エントリを追跡する必要がある新しい要件があるため、条件によっては、最も古い最初の注文で削除される可能性があります。ConcurrentLinkedQueueは良い選択であるように見え、機能的にはうまく機能します。

設定可能な数のエントリがメモリに保持され、制限に達したときに新しいエントリが提供されると、キューは最も古いものから順に検索され、削除できるエントリが最初に検索されます。特定のエントリはシステムによって削除されず、クライアントとの対話を待ちます。

発生しているように見えるのは、キューの先頭に発生したエントリがあります。たとえば、10万エントリ前です。キューには構成されたエントリの数が制限されているようです(size()== 100)が、プロファイリング時に、メモリ内に最大100KのConcurrentLinkedQueue$Nodeオブジェクトがあることがわかりました。これは仕様によるもののようで、ConcurrentLinkedQueueのソースを一瞥するだけで、削除すると、保存されているオブジェクトへの参照が削除されるだけで、リンクリストは繰り返しのために残されます。

最後に私の質問:この性質のコレクションを処理するための「より良い」怠惰な方法はありますか?私はConcurrentLinkedQueueの速度が大好きです。この場合、可能と思われる無制限のリークを許容することはできません。そうでない場合は、順序を追跡するために2番目の構造を作成する必要があり、同じ問題に加えて同期の問題が発生する可能性があります。

4

3 に答える 3

11

ここで実際に行われているのは、removeメソッドが、リンクされた参照をnullにするためのポーリングスレッドを準備することです。

ConcurrentLinkedQueueは、非ブロッキングスレッドセーフキューの実装です。ただし、キューからノードをポーリングしようとすると、2つの機能を持つプロセスになります。最初に値をnullにし、次に参照をnullにします。CAS操作は、ポーリングの即時解決を提供しない単一の不可分操作です。

ポーリングすると、成功した最初のスレッドがノードの値を取得し、その値をnullにすると、そのスレッドは参照をnullにしようとします。その後、別のスレッドが入り、キューからポーリングを試みる可能性があります。このキューが非ブロッキングプロパティを保持していることを確認するために(つまり、あるスレッドの失敗が別のスレッドの失敗につながることはありません)、新しい着信スレッドは値がnullかどうかを確認し、nullの場合、そのスレッドは参照をnullにして試行します再びpoll()に。

したがって、ここで発生しているのは、removeスレッドが、参照をnullにするための新しいポーリングスレッドを準備しているだけです。ノンブロッキングの削除機能を実現しようとすると、3つのアトミック機能が必要になるため、ほぼ不可能だと思います。値のnullは、そのノードを参照しているnullであり、最後に、そのノードからその後続ノードの親への新しい参照です。

あなたの最後の質問に答えるために。キューの非ブロッキング状態を削除して維持するためのより良い方法はありません。それは少なくともこの時点ではです。プロセッサが2ウェイおよび3ウェイのケーシングで出始めたら、それは可能です。

于 2010-03-30T18:24:41.933 に答える
1

キューの主なセマンティクスはadd/pollです。ConcurrentLinkedQueueでpoll()を使用すると、必要に応じてクリーンアップされます。説明に基づいて、poll()は最も古いエントリを削除する必要があります。remove()の代わりに使用しないのはなぜですか?

于 2010-03-30T18:25:37.823 に答える
1

1.6.0_29のソースコードを見ると、CLQのイテレータが変更されてnullアイテムのあるノードを削除しようとしているようです。それ以外の:

p = p.getNext();

コードは次のとおりです。

Node<E> next = succ(p);
if (pred != null && next != null)
    pred.casNext(p, next);
p = next; 

これは、バグの修正の一部として追加されました:http: //bugs.sun.com/view_bug.do?bug_id=6785442

実際、次のことを試してみると、古いバージョンではOOMEが得られますが、新しいバージョンでは得られません。

Queue<Integer> queue = new ConcurrentLinkedQueue<Integer>();
for (int i=0; i<10000; i++)
{
    for (int j=0; j<100000; j++)
    {
        queue.add(j);
    }
    boolean start = true;
    for (Iterator<Integer> iter = queue.iterator(); iter.hasNext(); )
    {
        iter.next();
        if (!start)
            iter.remove();
        start = false;
    }
    System.out.println(i);
}
于 2012-01-09T10:46:03.977 に答える