BlockingQueue的实现:SynchronousQueue和LinkedBlockingQueue之间有什么区别

时间:2011-02-24 09:12:43

标签: java collections concurrency java.util.concurrent

我看到 BlockingQueue 的这些实现,无法理解它们之间的差异。我的结论到目前为止:

  1. 我不会需要 SynchronousQueue
  2. LinkedBlockingQueue 确保必须使用参数true创建FIFO, BlockingQueue 以使其成为FIFO
  3. SynchronousQueue 会破坏大多数收藏方法(包含,尺寸等)
  4. 那么我什么时候需要 SynchronousQueue ?此实现的性能是否优于 LinkedBlockingQueue

    为了使它更复杂......为什么 Executors.newCachedThreadPool 在其他人使用SynchronousQueue时( Executors.newSingleThreadExecutor Executors.newFixedThreadPool )使用LinkedBlockingQueue?

    修改

    第一个问题已经解决。但是我仍然不明白为什么 Executors.newCachedThreadPool 在其他人( Executors.newSingleThreadExecutor Executors.newFixedThreadPool )使用LinkedBlockingQueue时会使用SynchronousQueue?

    我得到的是,使用SynchronousQueue,如果没有免费线程,生产者将被阻止。但由于线程数实际上是无限的(如果需要,将创建新线程),这将永远不会发生。那为什么要使用SynchronousQueue?

3 个答案:

答案 0 :(得分:52)

SynchronousQueue是一种非常特殊的队列 - 它在Queue的接口后面实现了一种集合方法(生成器等待消费者准备就绪,消费者等待生产者准备就绪)。

因此,只有在需要特定语义的特殊情况下才需要它,例如Single threading a task without queuing further requests

使用SynchronousQueue的另一个原因是性能。 SynchronousQueue的实现似乎已经过大量优化,因此如果您不需要任何更多的集合点(如Executors.newCachedThreadPool()的情况,那么“按需”创建消费者,以便队列项不累积),您可以使用SynchronousQueue获得性能提升。

简单的综合测试表明,在一个简单的单一生产者中 - 双核机器上的单个消费者场景SynchronousQueue的吞吐量比带队列的LinkedBlockingQueueArrayBlockingQueue的吞吐量高约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

答案 1 :(得分:5)

目前,默认的Executors(基于ThreadPoolExecutor)可以使用一组预先创建的固定大小的线程和一些大小的BlockingQueue来进行任何溢出或创建线程如果(且仅当)该队列已满,则为最大大小。

这会带来一些令人惊讶的特性。例如,由于只有在达到队列容量时才创建其他线程,因此使用LinkedBlockingQueue(无限制)意味着即使当前池大小也会永远创建新线程是零。如果您使用ArrayBlockingQueue,那么只有新线程已满,才会创建新线程,并且如果池在那时尚未清空空间,则有可能会拒绝后续作业。

SynchronousQueue的容量为零,因此生产者会阻塞,直到消费者可用,或创建一个线程。这意味着尽管@axtavt产生了令人印象深刻的数字,但从生产者的角度来看,缓存的线程池通常具有最差的性能。

不幸的是,目前还没有一个很好的库版本的折衷实现,它会在突发或活动期间创建线程,从最低的最低值开始。你有一个可成长的游泳池或一个固定的游泳池。我们内部有一个,但还没有为公众消费做好准备。

答案 2 :(得分:4)

缓存线程池按需创建线程。它需要一个队列,该队列将任务传递给等待的消费者或失败。如果没有等待的消费者,它会创建一个新的线程。 SynchronousQueue不包含元素,而是传递元素或失败。