31

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

例如。

class A {

    private volatile AtomicInteger count;

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

这是线程安全的吗?

4

5 回答 5

64

我相信这Atomic*实际上给出原子性和波动性。因此,当您致电 (say) 时AtomicInteger.get(),您一定会获得最新的价值。这记录在java.util.concurrent.atomic 包文档中:

原子访问和更新的记忆效应通常遵循 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*对象的事实意味着它们将看到该对象中的最新值。

于 2013-01-15T13:19:07.693 回答
6

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

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

  1. count = 9目前。
  2. 线程 A 运行if(count.get() <10)并获取true并停在那里。
  3. 线程 B 运行if(count.get() <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
}
于 2014-08-30T06:45:17.230 回答
2

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

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。

于 2014-12-02T13:03:35.460 回答
1

答案在这段代码中

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

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

于 2014-11-23T07:05:51.057 回答
0

您的查询可以分两部分回答,因为您的查询中有 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”做一个get并检查大小。有可能在 getAndIncrement 执行之前(例如,由线程 1),另一个线程(例如线程 2)启动并将计数增加到 10,然后退出;之后,您的线程 1 将恢复并将计数增加到 11。这是错误输出。这是因为您的“someMethod”无论如何都不会受到同步问题的保护。我仍然建议您观看 john purcell 的视频,看看 volatile 失败的地方,以便您更好地理解关键字 volatile。在他的例子中用 atomicinteger 替换它,看看它的神奇之处。

于 2019-08-29T16:13:46.763 回答