AtomicInteger和volatile

时间:2013-01-15 13:12:21

标签: java multithreading thread-safety atomic volatile

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

EG。

class A {

    private volatile AtomicInteger count;

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

这线程安全吗?

5 个答案:

答案 0 :(得分:49)

我相信Atomic*实际上给出了两者原子性和波动性。因此,当您致电(比方说)AtomicInteger.get()时,您可以保证获得最新的值。这在java.util.concurrent.atomic package documentation

中有记录
  

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

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

现在,如果你有

volatile AtomicInteger count;

volatile部分表示每个帖子都会使用最新的AtomicInteger引用,而且它是AtomicInteger这意味着您该对象的最新值。

需要这样做并不常见(IME) - 因为通常你不会重新分配count来引用不同的对象。相反,你有:

private final AtomicInteger count = new AtomicInteger();

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

答案 1 :(得分:2)

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

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

    目前
  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,这在单线程模式下永远不会发生。
  5. 如果您想在不使用较慢的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
    }
    

答案 2 :(得分:1)

此代码中有答案

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

这是AtomicInteger的源代码。 该值为Volatile。 因此,AtomicInteger在内部使用Volatile。

答案 3 :(得分:1)

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

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 :(得分:0)

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

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

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

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

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

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

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