将volatile关键字与可变对象一起使用

时间:2011-01-06 11:19:53

标签: java concurrency volatile mutable

在Java中,我了解volatile关键字提供对变量的可见性。问题是,如果变量是对可变对象的引用,volatile是否也提供对该对象内成员的可见性?

在下面的示例中,如果多个线程正在访问volatile Mutable m并更改value,那么它是否正常工作?

例如

class Mutable {
    private int value;
    public int get()
    {
        return a;
    }
    public int set(int value)
    {
        this.value = value;
    }
}

class Test {
    public volatile Mutable m;
}

5 个答案:

答案 0 :(得分:17)

这是关于volatile的一些细节的侧面说明。在这里写这个是因为评论太多了。我想举一些例子来说明易变性如何影响可见性,以及jdk 1.5中的变化情况。

给出以下示例代码:

public class MyClass
{
  private int _n;
  private volatile int _volN;

  public void setN(int i) {
    _n = i;
  }
  public void setVolN(int i) {
    _volN = i;
  }
  public int getN() { 
    return _n; 
  }
  public int getVolN() { 
    return _volN; 
  }

  public static void main() {
    final MyClass mc = new MyClass();

    Thread t1 = new Thread() {
      public void run() {
        mc.setN(5);
        mc.setVolN(5);
      }
    };

    Thread t2 = new Thread() {
      public void run() {
        int volN = mc.getVolN();
        int n = mc.getN();
        System.out.println("Read: " + volN + ", " + n);
      }
    };

    t1.start();
    t2.start();
  }
}

此测试代码的行为在jdk1.5 +中已明确定义,但在未定义良好。

在pre-jdk1.5世界中,易失性访问和非易失性访问之间没有明确的关系。因此,该计划的输出可能是:

  1. 阅读:0,0
  2. 阅读:0,5
  3. 阅读:5,0
  4. 阅读:5,5
  5. 在jdk1.5 +世界中,volatile的语义被改变,因此易失性访问以与同步完全相同的方式影响非易失性访问。因此,在jdk1.5 +世界中只有某些输出是可能的:

    1. 阅读:0,0
    2. 阅读:0,5
    3. 阅读:5,0 < - 不可能
    4. 阅读:5,5
    5. 输出3.是不可能的,因为从volatile_volN读取“5”会在2个线程之间建立同步点,这意味着在分配给_volN 之前所取的所有操作都必须对t2可见。

      进一步阅读:

答案 1 :(得分:8)

在您的示例中,volatile关键字仅保证任何线程写入“m”的最后一个引用对随后读取“m”的任何线程都可见。

它不保证您的 get()

所以使用以下序列:

Thread-1: get()     returns 2
Thread-2: set(3)
Thread-1: get()    

你回到2而不是3是完全合法的。volatile不会改变任何东西。

但是如果你将Mutable课改为:

class Mutable {
    private volatile int value;
    public int get()
    {
        return a;
    }
    public int set(int value)
    {
        this.value = value;
    }
}

然后保证Thread-1中的第二个get()将返回3.

但请注意,volatile通常不是最佳同步方法。

在你简单的get / set示例中(我知道它只是一个例子)像AtomicInteger这样的类,使用适当的同步并实际提供有用的方法,会更好。

答案 2 :(得分:5)

volatile仅提供对所声明的Object的引用的保证。该实例的成员不会同步。

根据Wikipedia,你有:

  
      
  • (在所有版本的Java中)读取和存在全局排序   写入易失性变量。这个   暗示每个线程访问一个   volatile字段将读取其当前值   继续前的价值,而不是   (可能)使用缓存值。   (但是,没有保证   易失性的相对排序   读取和写入常规读取   写道,意思是它   通常不是一个有用的线程   构造。)
  •   
  • (在Java 5或更高版本中)易失性读写会建立先发生的事件   关系,很像收购和   释放互斥锁。
  •   

所以基本上你所拥有的是通过声明字段volatile,与它交互会创建一个“同步点”,之后任何更改都将在其他线程中可见。但在此之后,使用get()set()是未经同步的。 Java Spec有更全面的解释。

答案 3 :(得分:0)

volatile没有“提供可见性”。它唯一的作用是阻止变量的处理器缓存,从而在并发读写时提供事先发生的关系。它不会影响对象的成员,也不会提供任何同步 synchronized锁定。

由于您没有告诉我们您的代码的“正确”行为是什么,因此无法回答这个问题。

答案 4 :(得分:0)

使用volatile而不是完全synchronized值本质上是一种优化。与volatile访问相比,优化来自为synchronized值提供的较弱保证。过早的优化是万恶之源;在这种情况下,邪恶可能很难追查,因为它将以竞争条件等形式出现。所以如果你需要问,你可能不应该使用它。