私はBlockingQueueのこれらの実装を見て、それらの違いを理解できません。これまでの私の結論:
それで、いつSynchronousQueueが必要になるのですか?この実装のパフォーマンスはLinkedBlockingQueueより優れていますか?
より複雑にするために、なぜExecutors.newCachedThreadPoolが他のときにSynchronousQueueを使用するか(Executors.newSingleThreadExecutorおよびExecutors.newFixedThreadPool)はLinkedBlockingQueueを使用する?
[〜#〜] edit [〜#〜]
最初の質問は解決されました。しかし、なぜExecutors.newCachedThreadPoolがSynchronousQueueを使用するのか(Executors.newSingleThreadExecutorおよびExecutors.newFixedThreadPool)がLinkedBlockingQueueを使用するのか)はまだわかりません。
SynchronousQueueを使用すると、空きスレッドがない場合、プロデューサーはブロックされます。ただし、スレッドの数は実質的に無制限であるため(必要に応じて新しいスレッドが作成されます)、これは起こりません。では、なぜSynchronousQueueを使用する必要があるのでしょうか。
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
}
}
そして、これが私のマシンでの結果です:
現在、デフォルトのExecutors
(ThreadPoolExecutor
ベース)は、事前に作成された固定サイズのスレッドのセットと、オーバーフロー用にいくつかのサイズのBlockingQueue
を使用するか、スレッドを作成できます。キューがいっぱいの場合(およびその場合のみ)の最大サイズ。
これは、いくつかの驚くべき特性につながります。たとえば、キューの容量に達したときにのみ追加のスレッドが作成されるため、LinkedBlockingQueue
(無制限)を使用すると、現在のプールであっても新しいスレッドがnever作成されますサイズはゼロです。 ArrayBlockingQueue
を使用する場合、新しいスレッドは、それがいっぱいである場合にのみ作成されます。プールがそれまでにスペースをクリアしていない場合、後続のジョブが拒否される可能性があります。
SynchronousQueue
の容量はゼロなので、プロデューサーは、コンシューマーが利用可能になるか、スレッドが作成されるまでブロックします。つまり、@ axtavtによって生成された印象的な外観にもかかわらず、キャッシュされたスレッドプールは一般に、プロデューサーの観点から見て最悪のパフォーマンスを示します。
残念ながら、バーストまたはアクティビティ中に最大値から最小値までのスレッドを作成する妥協実装のNiceライブラリバージョンは現在ありません。拡張可能なプールまたは固定プールがあります。内部には1つありますが、まだ一般消費する準備が整っていません。
キャッシュスレッドプールは、オンデマンドでスレッドを作成します。タスクを待機中のコンシューマーに渡すか、失敗するキューが必要です。待機中のコンシューマがない場合は、新しいスレッドを作成します。 SynchronousQueueは要素を保持せず、代わりに要素を渡すか、失敗します。