【问题标题】:AtomicInteger and volatile [duplicate]AtomicInteger 和 volatile [重复]
【发布时间】:2012-12-29 14:22:06
【问题描述】:

我知道volatile 允许可见性,AtomicInteger 允许原子性。 那么如果我使用 volatile AtomicInteger,是否意味着我不必再使用任何同步机制?

例如。

class A {

    private volatile AtomicInteger count;

    void someMethod(){
        // do something
        if(count.get() < 10) {
            count.incrementAndGet();
        }
}

这是线程安全的吗?

【问题讨论】:

  • 这里的“线程安全”是什么意思?不能保证上面的代码意味着count永远不会超过10,例如——多个线程可以调用intValue(),然后全部调用incrementAndGet()
  • 没错,这会起作用吗? if(count.get()
  • 在您提出要求之前,我无法回答什么会“有效”。会编译吗?是的。它会避免任何增量丢失吗?是的。它会做你想做的事吗?我说不出来。
  • 我的意思是线程安全的?这意味着它不会丢失更新吗?
  • 是的 - 但您最终可能会得到大于 10 的计数。

标签: java multithreading thread-safety atomic volatile


【解决方案1】:

我相信Atomic* 实际上赋予了原子性和易变性。因此,当您致电(比如说)AtomicInteger.get() 时,您一定会获得 latest 值。这记录在java.util.concurrent.atomic package documentation:

原子访问和更新的记忆效应通常遵循 volatile 规则,如 Java™ 语言规范第 17.4 节所述。

  • get 具有读取 volatile 变量的记忆效应。
  • set 具有写入(分配)易失性变量的记忆效应。
  • lazySet 具有写入(分配)易失性变量的记忆效应,但它允许对后续(但不是先前)内存操作进行重新排序,这些操作本身不会对普通的非易失性写入施加重新排序约束。在其他使用上下文中,> -lazySet 可能在清空时应用,为了垃圾收集,一个永远不会再次访问的引用。
  • weakCompareAndSet 以原子方式读取和有条件地写入变量,但不会创建任何发生前的顺序,因此不保证之前或后续读取和写入除weakCompareAndSet 目标之外的任何变量。
  • compareAndSet 和所有其他读取和更新操作(例如 getAndIncrement)具有读取和写入 volatile 变量的记忆效应。

现在如果你有

volatile AtomicInteger count;

volatile 部分意味着每个线程将使用最新的 AtomicInteger 引用,而它是 AtomicInteger 的事实意味着您将看到该对象的最新值.

这并不常见 (IME) - 因为通常您不会重新分配 count 来引用不同的对象。相反,您将拥有:

private final AtomicInteger count = new AtomicInteger();

此时,它是 final 变量的事实意味着所有线程将处理同一个对象 - 而它是 Atomic* 对象的事实意味着它们将看到该对象中的最新值。

【讨论】:

  • 好的,我已经稍微修改了这个问题,你能解释一下这是否成立吗?
  • @anirbanchowdhury:你为什么要使用intValue()而不是get
  • Rite,我的错,已将原问题更新为get()
  • 非常感谢。这正是我想要的
  • 在 AtomicInteger 中保存值的真实字段毫无疑问是易变的!
【解决方案2】:

我会说不,它不是线程安全的,如果您将线程安全定义为在单线程模式和多线程模式下具有相同的结果。在单线程模式下,计数永远不会超过 10,但在多线程模式下可以。

问题在于 getincrementAndGet 是原子的,但 if 不是。请记住,非原子操作可以随时暂停。例如:

  1. count = 9 目前。
  2. 线程 A 运行 if(count.get() &lt;10) 并获取 true 并停在那里。
  3. 线程 B 运行if(count.get() &lt;10) 并获得true,因此它运行count.incrementAndGet() 并完成。现在count = 10
  4. 线程 A 恢复并运行 count.incrementAndGet(),现在是 count = 11,这在单线程模式下永远不会发生。

如果您想在不使用速度较慢的synchronized 的情况下使其线程安全,请尝试以下实现:

class A{

final AtomicInteger count;

void someMethod(){
// do something
  if(count.getAndIncrement() <10){
      // safe now
  } else count.getAndDecrement(); // rollback so this thread did nothing to count
}

【讨论】:

    【解决方案3】:

    要保持原始语义并支持多线程,您可以执行以下操作:

    public class A {
    
        private AtomicInteger count = new AtomicInteger(0);
    
        public void someMethod() {
    
            int i = count.get();
            while (i < 10 && !count.compareAndSet(i, i + 1)) {
                i = count.get();
            }
    
        }
    
    }
    

    这避免了任何线程看到计数达到 10。

    【讨论】:

      【解决方案4】:

      答案就在这段代码里

      http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/atomic/AtomicInteger.java

      这是 AtomicInteger 的源代码。 该值是易变的。 所以,AtomicInteger 在里面使用了 Volatile。

      【讨论】:

        【解决方案5】:

        您的查询可以分两部分回答,因为您的查询中有 2 个问题:

        1) 参考 Oracle 的原子变量教程文档: https://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html

        java.util.concurrent.atomic 包定义了支持对单个变量进行原子操作的类。所有类都有 get 和 set 方法,其工作方式类似于对 volatile 变量的读取和写入。也就是说,一个集合与同一变量上的任何后续获取具有发生前的关系。原子 compareAndSet 方法也具有这些内存一致性功能,适用于整数原子变量的简单原子算术方法也是如此。

        所以原子整数确实在内部使用 volatile,正如这里的其他答案所提到的。因此,使您的原子整数易失是没有意义的。您需要同步您的方法。

        您应该在 Udemy 上观看 John Purcell 的免费视频,其中显示了当多个线程尝试修改 volatile 关键字时失败。简单而美丽的例子。 https://www.udemy.com/course/java-multithreading/learn/lecture/108950#overview

        如果您将 John 示例中的 volatile 计数器更改为原子变量,则可以保证他的代码成功,而无需像他在教程中所做的那样使用 sunchronized 关键字

        2) 来到您的代码: 假设线程 1 开始运行,“someMethod”执行获取并检查大小。有可能在 getAndIncrement 执行之前(例如,由线程 1),另一个线程(例如线程 2)启动并将计数增加到 10,然后退出;之后,您的线程 1 将恢复并将计数增加到 11。这是错误输出。这是因为您的“someMethod”无论如何都不会受到同步问题的保护。 我仍然建议您观看 john purcell 的视频,看看 volatile 失败的地方,以便您更好地理解关键字 volatile。在他的例子中用 atomicinteger 替换它,看看它的神奇之处。

        【讨论】:

          猜你喜欢
          • 2013-02-15
          • 2016-12-26
          • 2014-06-26
          • 1970-01-01
          • 2017-08-26
          • 2015-12-28
          • 2017-08-29
          • 2013-03-25
          • 1970-01-01
          相关资源
          最近更新 更多