我看到 BlockingQueue 的这些实现,无法理解它们之间的差异。我的结论到目前为止:
那么我什么时候需要 SynchronousQueue ?此实现的性能是否优于 LinkedBlockingQueue ?
为了使它更复杂......为什么 Executors.newCachedThreadPool 在其他人使用SynchronousQueue时( Executors.newSingleThreadExecutor 和 Executors.newFixedThreadPool )使用LinkedBlockingQueue?
修改 的
第一个问题已经解决。但是我仍然不明白为什么 Executors.newCachedThreadPool 在其他人( Executors.newSingleThreadExecutor 和 Executors.newFixedThreadPool )使用LinkedBlockingQueue时会使用SynchronousQueue?
我得到的是,使用SynchronousQueue,如果没有免费线程,生产者将被阻止。但由于线程数实际上是无限的(如果需要,将创建新线程),这将永远不会发生。那为什么要使用SynchronousQueue?
答案 0 :(得分:52)
SynchronousQueue
是一种非常特殊的队列 - 它在Queue
的接口后面实现了一种集合方法(生成器等待消费者准备就绪,消费者等待生产者准备就绪)。
因此,只有在需要特定语义的特殊情况下才需要它,例如Single threading a task without queuing further requests。
使用SynchronousQueue
的另一个原因是性能。 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
}
}
这是我机器上的结果:
答案 1 :(得分:5)
目前,默认的Executors
(基于ThreadPoolExecutor
)可以使用一组预先创建的固定大小的线程和一些大小的BlockingQueue
来进行任何溢出或创建线程如果(且仅当)该队列已满,则为最大大小。
这会带来一些令人惊讶的特性。例如,由于只有在达到队列容量时才创建其他线程,因此使用LinkedBlockingQueue
(无限制)意味着即使当前池大小也会永远创建新线程是零。如果您使用ArrayBlockingQueue
,那么只有新线程已满,才会创建新线程,并且如果池在那时尚未清空空间,则有可能会拒绝后续作业。
SynchronousQueue
的容量为零,因此生产者会阻塞,直到消费者可用,或创建一个线程。这意味着尽管@axtavt产生了令人印象深刻的数字,但从生产者的角度来看,缓存的线程池通常具有最差的性能。
不幸的是,目前还没有一个很好的库版本的折衷实现,它会在突发或活动期间创建线程,从最低的最低值开始。你有一个可成长的游泳池或一个固定的游泳池。我们内部有一个,但还没有为公众消费做好准备。
答案 2 :(得分:4)
缓存线程池按需创建线程。它需要一个队列,该队列将任务传递给等待的消费者或失败。如果没有等待的消费者,它会创建一个新的线程。 SynchronousQueue不包含元素,而是传递元素或失败。