【问题标题】:Inserting Incrementing value into an ArrayBlockingQueue and thread saftey将递增值插入 ArrayBlockingQueue 和线程安全
【发布时间】:2017-08-12 00:01:22
【问题描述】:

我知道下面代码中的递增不是原子的。我希望递增、插入阻塞队列和打印计数器的值都是原子操作。我知道原子 int,但我正在尝试使用同步来使其工作以用于学习目的。

int counter = 0;
BlockingQueue<Integer> numbers = new ArrayBlockingQueue<Integer>(100);

while(true) {
    numbers.put(counter++);
    System.out.println("Inserted new number: " + counter);
}
... // other code that may take things off the numbers queue..

如果我定义一个名为 increment() 的同步方法,该方法递增、打印和返回并将其传递给 numbers.put(increment());,会怎样?在那种情况下,增加、打印和插入阻塞队列会是一个原子操作吗?

【问题讨论】:

  • 插入会,增量不会,因为它不同步。
  • 现在是我们退一步问的时候了,为什么要以原子方式在阻塞队列中插入数字?我的意思是,除了深奥的原因之外,这里有什么用例(如果有的话)?
  • 用例 = 学习....
  • 您得到了各种形式的答案。 put(counter++) 不是原子的,涉及 2 个不同的操作。为了使其原子化,您需要一个外部监视器。还有什么?

标签: java multithreading synchronized


【解决方案1】:

如果我定义一个名为 increment() 的同步方法,该方法递增、打印和返回并将其传递给 numbers.put(increment()); 会怎样?在那种情况下,增加、打印和插入阻塞队列会是一个原子操作吗?

不,您需要同步两个操作。

否则可能出现以下流程:

  1. 线程A调用increment(取回1)
  2. 线程 B 调用 increment(取回 2,该部分有效,因为您同步了方法,它不会再获得 1)
  3. 线程 B 添加到队列(现在队列在 1 之前有 2)
  4. 线程 A 加入队列

这里发生的事情是线程 B “潜入”了线程 A 的两个操作之间。即使您使用 AtomicInteger 来实现计数器,也会发生完全相同的事情。防止这种无效的交错执行的唯一方法是将这两个操作组合在一起,放在一个锁后面。

【讨论】:

  • 谢谢蒂洛。这样就搞清楚了。但是,如果我有一个生产者消费者类型的东西,在生产者线程中,增量正在发生,而在消费者线程中,数字的删除正在发生,是否存在问题?在那种情况下,我们会遇到任何只有 2 个线程的竞争条件吗?一个线程,生产者,另一个线程消费者?您提到的情况只有在有 2 个生产者线程时才会有问题,对吗?
  • 如果您只有一个线程可以通过代码路径运行,您不需要使该代码路径线程安全,是的。在您的示例中,如果只有一个线程与计数器对话并添加到队列中,则不需要同步。永远只有线程 A,没有线程 B。
  • 你是幸运的。谢谢你。最后一个问题!如果我只使用 atomic int 怎么办?那么不管有多少流通过递增、打印和插入该队列的过程,它应该是线程安全的,因为原子 int?
  • 不,您需要同步这两个操作。使用 AtomicInteger,上述交错执行仍然是可能的。 AtomicInteger 仅保证您不会两次获得相同的数字(或丢失的数字),就像同步 increase 方法一样。
  • 谢谢蒂洛。但是你提到我需要在这两种方法上同步。我该怎么做?我需要做synchronized(lock) { numbers.put(counter++);,即使它进入一个arrayblockingqueue...这不是多余的和太多的同步吗?
猜你喜欢
  • 2015-07-14
  • 2023-02-01
  • 2020-08-24
  • 2020-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多