【问题标题】:ArrayBlockingQueue: concurrent put and takeArrayBlockingQueue:并发put和take
【发布时间】:2014-08-23 20:48:55
【问题描述】:

为什么没有使用LinkedBlockingQueue的方式实现ABQ。我们可以使用 AtomicInteger 来保持 ABQ 中的 Track 计数,这与 LBQ 一样。我们也可以将两把锁用于 ABQ。 我偶然发现了关于 SO 的类似问题。 ArrayBlockingQueue uses a single lock for insertion and removal but LinkedBlockingQueue uses 2 separate locks

但我无法理解该问题的答案。我需要帮助来理解如果我们使用两个锁实现 ABQ 会出现的问题。 如果有人可以举例说明可能失败的竞争条件,那就太好了。 这个问题可以标记为重复,但我真的在寻找更具描述性的答案。这将是一个很大的帮助。

我在这里粘贴了一个代码 http://pastebin.com/ZD1uFy7S 。任何人都可以显示粘贴的代码中是否可能存在竞争条件。

【问题讨论】:

  • 这个问题几乎可以肯定对于 StackOverflow 来说太宽泛了。您正在询问算法的设计。 StackOverflow 应该针对特定的编程问题:例如,“我尝试了这个......示例代码......当我期望它做 Y 时它做了 X。什么给出了?”
  • 如果有两个锁,你可以有更好的吞吐量,就像在 LinkedBlockingQueue 的情况下一样。生产者不需要与消费者竞争
  • @user3580294,不正确! LinkedBlockingQueue 不使用两个锁,因为一个不足:一个锁可以提供足够的安全性,但两个可以(在那个算法中)提供相同级别的安全性,但具有更好的安全性性能水平。
  • 在 Array 实现中,两个锁无法提供足够的安全性,因为队列的头尾之间的关系比链接队列中的耦合更紧密。
  • @jameslarge 实际上并没有要求整个算法,而只是它不使用与 LBQ 相同的策略的一个很好的理由

标签: java multithreading concurrency java.util.concurrent blockingqueue


【解决方案1】:

ArrayBlockingQueue,根据定义,当 put() 发生且其固定数组已满时阻塞等待空间可用

可用的空间是从 take() 返回的元素。换句话说,固定数组中的元素会随着时间的推移而被重用。 put() 必须将其项目写入数组中的特定位置

另一方面,LinkedBlockingQueue 是一个链表。对于本次讨论,假设您创建了一个有界的,只是为了使其更类似于 ArrayBlockingQueue。尝试同样的事情:

put() 当 LinkedBlockingQueue 已满时放置一个元素。它将等待一个元素可用。

但在这种情况下,当您执行 take() 时 - 它只会返回 head 值并核对该项目。然后 put() 看到 LinkedBlockingQueue 低于容量。它将其项目链接到列表的尾部。不会像 ArrayBlockingQueue 那样覆盖内存,它必须保持连续。

编辑:这是一种假设性的练习,因为代码不是这样编写的。但无论如何,这里有更多细节,特别是插入和提取方法: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/ArrayBlockingQueue.java

潜在问题 IF 使用了 2 个锁,AND 现有代码或多或少保持不变:

ArrayBlockingQueue 已满

线程 1:调用 take(),获取锁 A,做它的事情,递减计数到 [容量-1]——但还没有完成

线程 2:调用 put(),在 T1 仍在运行时获取锁 B,将计数增加到 [容量],释放锁 B

线程 1:信号 notFull()

线程 3:put() 开始执行,即使数组确实已满,也会覆盖一个元素,因为 ArrayBlockingQueue 使用循环增量。

这种情况不会发生在 LinkedBlockingQueue 中。

【讨论】:

  • 当队列已满时我想说的是 atomicCount == 容量,即所有生产者都等到其中一个消费者减少计数。为此,我认为我不需要为每个看跌期权持有消费者。另外,我认为您所说的可能是有道理的,但是仍然缺少某些东西我无法理解..可能是带有可能竞争条件的小代码 sn-p 会有所帮助
  • 可能我无法完全理解,但是您展示的 utrace 条件可以通过 while 循环检查队列已满的条件非常简单地避免。我在粘贴箱中粘贴了一个代码,以显示我在问什么pastebin.com/ZD1uFy7S。您解释的竞态条件不会发生在上面粘贴的代码中。
  • 我还没有在实际使用中逐步完成 pastebin 代码,但我看到的问题是同一个问题:计数增量/减量(即使使用 AtomicInteger)是来自 notFull / 的单独调用notEmpty.signal()。如果 put() 和 take() 使用不同的锁,那么 put() 可能会增加 count,然后 take 会减少它并在 put 信号之前发出信号。或相反亦然。不知道如何才能更清楚地解释它。
  • @jthalborn 如果你要打折这个场景,你应该解释原因。
  • @spudone 不,您会看到等待时有一个 while 循环,因此它始终会查看正确的计数。如果不是这种情况,即使 LBQ 会在不覆盖元素但队列超过其大小的意义上失败
【解决方案2】:

我研究了一下。我得出的结论是,ABQ 可以像 LBQ 一样实现。我在粘贴箱中粘贴了一个代码以显示http://pastebin.com/ZD1uFy7S。 不以这种方式实现的主要原因是因为代码变得越来越复杂,尤其是因为支持迭代器和性能优势对于更复杂的实现来说并不显着。

【讨论】:

  • 一个问题是您的代码向put()take() 路径添加了一个原子增量(实际上是一个CAS 循环)。对于无竞争的场景,这将比更简单的单锁方法慢,并且无竞争的场景很重要。对于更快的竞争场景将取决于您使用的确切细节。
  • 这一行的许多实现完全避免了 count 变量,但这带来了另一个安全问题。
猜你喜欢
  • 1970-01-01
  • 2011-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-27
相关资源
最近更新 更多