web-dev-qa-db-ja.com

BlockingQueueの実装:SynchronousQueueとLinkedBlockingQueueの違いは何ですか

私はBlockingQueueのこれらの実装を見て、それらの違いを理解できません。これまでの私の結論:

  1. 私はこれまで必要としませんSynchronousQueue
  2. LinkedBlockingQueue FIFOを保証しますBlockingQueueそれをFIFOにするためにパラメーターtrueで作成する必要があります
  3. SynchronousQueueほとんどのコレクションメソッド(contains、sizeなど)を壊します

それで、いつSynchronousQueueが必要になるのですか?この実装のパフォーマンスはLinkedBlockingQueueより優れていますか?

より複雑にするために、なぜExecutors.newCachedThreadPoolが他のときにSynchronousQueueを使用するか(Executors.newSingleThreadExecutorおよびExecutors.newFixedThreadPool)はLinkedBlockingQueueを使用する?

[〜#〜] edit [〜#〜]

最初の質問は解決されました。しかし、なぜExecutors.newCachedThreadPoolがSynchronousQueueを使用するのか(Executors.newSingleThreadExecutorおよびExecutors.newFixedThreadPool)がLinkedBlockingQueueを使用するのか)はまだわかりません。

SynchronousQueueを使用すると、空きスレッドがない場合、プロデューサーはブロックされます。ただし、スレッドの数は実質的に無制限であるため(必要に応じて新しいスレッドが作成されます)、これは起こりません。では、なぜSynchronousQueueを使用する必要があるのでしょうか。

34
nanda

SynchronousQueueは非常に特殊な種類のキューです。Queueのインターフェースの背後でランデブーアプローチ(プロデューサーはコンシューマーの準備ができるまで待機し、コンシューマーはプロデューサーの準備ができるまで待機します)を実装します。

したがって、特定のセマンティクスが必要な特別な場合にのみ必要になる可能性があります。たとえば、 追加のリクエストをキューに入れずにタスクをシングルスレッド化する のようになります。

SynchronousQueueを使用するもう1つの理由は、パフォーマンスです。 SynchronousQueueの実装は大幅に最適化されているように思われるため、ランデブーポイント以上が必要ない場合(Executors.newCachedThreadPool()の場合のように、コンシューマーは「オンデマンド」で作成されます) 、キューアイテムが蓄積しないように)、SynchronousQueueを使用してパフォーマンスを向上させることができます。

単純な合成テストは、デュアルコアマシンでの単一のプロデューサー-シングルコンシューマーシナリオの場合、SynchronousQueueのスループットがLinkedBlockingQueueおよびArrayBlockingQueueのスループットよりも約20倍高いことを示していますlength =1。キューの長さが増えると、スループットが上がり、ほぼSynchronousQueueのスループットに達します。これは、マルチコアマシンでのSynchronousQueueの同期オーバーヘッドが他のキューと比較して低いことを意味します。ただし、ここでも、Queueに偽装したランデブーポイントが必要な特定の状況でのみ問題になります。

編集:

ここにテストがあります:

public class Test {
    static ExecutorService e = Executors.newFixedThreadPool(2);
    static int N = 1000000;

    public static void main(String[] args) throws Exception {    
        for (int i = 0; i < 10; i++) {
            int length = (i == 0) ? 1 : i * 5;
            System.out.print(length + "\t");
            System.out.print(doTest(new LinkedBlockingQueue<Integer>(length), N) + "\t");
            System.out.print(doTest(new ArrayBlockingQueue<Integer>(length), N) + "\t");
            System.out.print(doTest(new SynchronousQueue<Integer>(), N));
            System.out.println();
        }

        e.shutdown();
    }

    private static long doTest(final BlockingQueue<Integer> q, final int n) throws Exception {
        long t = System.nanoTime();

        e.submit(new Runnable() {
            public void run() {
                for (int i = 0; i < n; i++)
                    try { q.put(i); } catch (InterruptedException ex) {}
            }
        });    

        Long r = e.submit(new Callable<Long>() {
            public Long call() {
                long sum = 0;
                for (int i = 0; i < n; i++)
                    try { sum += q.take(); } catch (InterruptedException ex) {}
                return sum;
            }
        }).get();
        t = System.nanoTime() - t;

        return (long)(1000000000.0 * N / t); // Throughput, items/sec
    }
}    

そして、これが私のマシンでの結果です:

enter image description here

54
axtavt

現在、デフォルトのExecutorsThreadPoolExecutorベース)は、事前に作成された固定サイズのスレッドのセットと、オーバーフロー用にいくつかのサイズのBlockingQueueを使用するか、スレッドを作成できます。キューがいっぱいの場合(およびその場合のみ)の最大サイズ。

これは、いくつかの驚くべき特性につながります。たとえば、キュ​​ーの容量に達したときにのみ追加のスレッドが作成されるため、LinkedBlockingQueue(無制限)を使用すると、現在のプールであっても新しいスレッドがnever作成されますサイズはゼロです。 ArrayBlockingQueueを使用する場合、新しいスレッドは、それがいっぱいである場合にのみ作成されます。プールがそれまでにスペースをクリアしていない場合、後続のジョブが拒否される可能性があります。

SynchronousQueueの容量はゼロなので、プロデューサーは、コンシューマーが利用可能になるか、スレッドが作成されるまでブロックします。つまり、@ axtavtによって生成された印象的な外観にもかかわらず、キャッシュされたスレッドプールは一般に、プロデューサーの観点から見て最悪のパフォーマンスを示します。

残念ながら、バーストまたはアクティビティ中に最大値から最小値までのスレッドを作成する妥協実装のNiceライブラリバージョンは現在ありません。拡張可能なプールまたは固定プールがあります。内部には1つありますが、まだ一般消費する準備が整っていません。

5

キャッシュスレッドプールは、オンデマンドでスレッドを作成します。タスクを待機中のコンシューマーに渡すか、失敗するキューが必要です。待機中のコンシューマがない場合は、新しいスレッドを作成します。 SynchronousQueueは要素を保持せず、代わりに要素を渡すか、失敗します。

4
Peter Lawrey