【问题标题】:When should I use SynchronousQueue over LinkedBlockingQueue我什么时候应该在 LinkedBlockingQueue 上使用 SynchronousQueue
【发布时间】:2012-01-25 08:53:39
【问题描述】:
new SynchronousQueue()
new LinkedBlockingQueue(1)

有什么区别?我什么时候应该使用 SynchronousQueue 来对抗容量为 1 的 LinkedBlockingQueue?

【问题讨论】:

    标签: java concurrency blockingqueue


    【解决方案1】:

    SynchronousQueue 更像是一种切换,而 LinkedBlockingQueue 只允许单个元素。不同之处在于,对 SynchronousQueue 的 put() 调用不会返回,直到有相应的 take() 调用,但是对于大小为 1 的 LinkedBlockingQueue,put() 调用(对空队列) 将立即返回。

    我不能说我自己曾经直接使用过 SynchronousQueue,但它是用于 Executors.newCachedThreadPool() 方法的默认 BlockingQueue。当您真的不想要队列(您不想维护任何待处理的数据)时,它本质上是 BlockingQueue 实现。

    【讨论】:

    • 好的,所以主要思想是,它阻塞读取线程直到结果准备好,阻塞写入线程直到读取线程准备好读取。你能否提供一个现实生活中可能有用的例子
    • @Umar 当多个线程为队列生成对象的速度快于消费者消费和处理它们的速度时 - 队列的大小可能会过度增长。 SynchronousQueue 有助于控制通信,而无需生产者中的任何特定代码。在现实生活中,它类似于一个人回答其他人提出的问题的会议。将 SynchronousQueue 视为一种秘书。
    • 我使用 SynchronousQueue 的情况是在“流水线”场景中。假设您有一个阶段处理管道,其中一些数据块从“生产者”开始到“消费者”结束。假设所有阶段都具有一定的确定性,那么拥有一个实际的队列就有点过分了。您所需要的只是阶段之间的切换。如果数据块很大,这一点很重要,因为您不想创建太多的数据块。这类似于旧的“双缓冲”策略。
    • 一个非常具体的例子是数据库加载器。假设您要扫描一个分隔的文本文件并加载到数据库中。您有两个阶段 - 一个阶段扫描文件并生成“记录”块以进行插入(记录块可以是二维对象数组),另一个阶段调用 JDBC 进行记录插入,每个块都有自己的事务/批。这些东西很好地重叠。
    • 如果你将单线程操作分解为多个阶段,并将这些阶段与它们之间传递的工作项一起流水线化,并在其自己的线程中运行每个阶段,你将从重叠中获得并发.在这种情况下,“扫描 CSV”和“插入记录”是两个阶段,它们可以同时进行以使数据库插入饱和。
    【解决方案2】:

    据我了解,上面的代码做同样的事情。

    不,代码根本不一样。

    Sync.Q.需要有服务员才能成功。即使没有服务员,LBQ 将保留该项目并立即完成。

    SyncQ 对于任务切换很有用。想象一下,您有一个带有待处理任务的列表和 3 个可用线程在队列中等待,尝试使用列表的 1/4 的 offer(),如果不接受,线程可以自行运行任务。 [最后的 1/4 应该由当前线程处理,如果你想知道为什么是 1/4 而不是 1/3]

    考虑尝试将任务交给工作人员,如果没有可用的任务,您可以选择自己执行任务(或抛出异常)。相反,使用 LBQ,将任务留在队列中并不能保证任何执行。

    注意:消费者和发布者的情况是一样的,即发布者可以阻塞并等待消费者,但在offer或poll返回后,它确保任务/元素被处理。

    【讨论】:

      【解决方案3】:

      使用 SynchronousQueue 的一个原因是提高应用程序性能。如果您必须在线程之间进行切换,您将需要一些同步对象。如果你能满足它使用所需的条件,SynchronousQueue 是我找到的最快的同步对象。其他人同意。见:Implementation of BlockingQueue: What are the differences between SynchronousQueue and LinkedBlockingQueue

      【讨论】:

        【解决方案4】:

        SynchronousQueue 的工作方式类似,但主要区别如下: 1) SynchronousQueue 的大小为 0 2) put() 方法将仅在 take() 方法能够同时从队列中获取该元素的情况下插入一个元素,即如果消费者 take() 调用需要一些时间才能插入一个元素消费吧。

        SynchronousQueue - 仅当某人在那个时刻收到它时才插入。

        【讨论】:

          【解决方案5】:

          [只是试图用(可能)更清晰的词来表达。]

          我相信SynchronousQueue API docs 非常清楚地说明了事情:

          1. 一个阻塞队列,其中每个插入操作都必须等待另一个线程的相应删除操作,反之亦然。
          2. 同步队列没有任何内部容量,甚至没有一个容量。您无法查看同步队列,因为一个元素仅在您尝试删除它时才存在;除非另一个线程试图删除它,否则您不能插入元素(使用任何方法);你不能迭代,因为没有什么可以迭代的。
          3. 队列的头部是第一个排队的插入线程试图添加到队列中的元素;如果没有这样的排队线程,则没有可删除的元素,poll() 将返回 null。

          还有BlockingQueue API docs:

          1. 队列还支持在检索元素时等待队列变为非空,以及在存储元素时等待队列中可用空间的操作。

          所以区别很明显,而且有些微妙,尤其是下面的第 3 点:

          1. 如果从BlockingQueue 检索时队列为空,则操作会阻塞,直到插入新元素。此外,如果在插入BlockingQueue 时队列已满,则操作将阻塞,直到从队列中删除元素并为新队列腾出空间。但是请注意,在SynchronousQueue 中,操作被阻止以在另一个线程上发生相反的操作(插入和删除彼此相反)。 因此,与BlockingQueue 不同,阻塞取决于操作的存在,而不是元素的存在或不存在。
          2. 由于阻塞依赖于相反操作的存在,因此元素永远不会真正插入队列中。这就是为什么第二点:“同步队列没有任何内部容量,甚至没有容量。”
          3. 因此,peek() 始终返回 null(再次检查 API doc),iterator() 返回一个空迭代器,其中 hasNext() 始终返回 false。 (API doc)。但是,请注意poll() 方法巧妙地检索并删除此队列的头部,如果另一个线程当前正在使一个元素可用并且不存在这样的线程,则它返回null。 (API doc)

          最后一点,SynchronousQueue 和 LinkedBlockingQueue 类都实现了BlockingQueue 接口。

          【讨论】:

            【解决方案6】:

            同步队列主要用于切换目的。它们没有任何capacity,并且 put 操作被阻塞,直到某个其他线程执行 get 操作。

            如果我们想在两个线程之间安全地共享一个变量,我们可以将该变量放在同步队列中,让其他线程从队列中取出。

            来自https://www.baeldung.com/java-synchronous-queue的代码示例

                ExecutorService executor = Executors.newFixedThreadPool(2);
                SynchronousQueue<Integer> queue = new SynchronousQueue<>();
            Runnable producer = () -> {
                Integer producedElement = ThreadLocalRandom
                  .current()
                  .nextInt();
                try {
                    queue.put(producedElement);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            };
            
            Runnable consumer = () -> {
                try {
                    Integer consumedElement = queue.take();
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            };
            
            executor.execute(producer);
            executor.execute(consumer);
            
            executor.awaitTermination(500, TimeUnit.MILLISECONDS);
            executor.shutdown();
            assertEquals(queue.size(), 0);
            

            它们也用于 CachedThreadPool 以实现任务到达时无限(Integer.MAX)线程创建的效果。 CachedPool 的 coreSize 为 0,maxPoolSize 为 Integer.MAX,具有同步队列

            当任务到达队列时,其他任务会被阻塞,直到第一个任务被取出。由于它没有任何队列容量,线程池将创建一个线程,该线程将取出任务,允许将更多任务放入队列。这将一直持续到线程创建达到 maxPoolSize。根据 timeOut,空闲线程可能会被终止并在不超过 maxPoolSize 的情况下创建新线程。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2021-09-07
              • 2011-08-08
              • 2012-09-22
              • 1970-01-01
              • 2010-12-30
              • 2011-12-27
              相关资源
              最近更新 更多