32

在 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;
}
4

5 回答 5

17

这是对 volatile 一些细节的旁注解释。写在这里是因为评论太多了。我想举一些例子来说明 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+ 中已明确定义,但在 jdk1.5之前明确定义。

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

  1. 读取:0, 0
  2. 阅读:0、5
  3. 阅读:5, 0
  4. 阅读:5、5

在 jdk1.5+ 的世界中,volatile 的语义发生了变化,因此 volatile 访问影响 non-volatile 访问的方式与同步完全相同。因此,在 jdk1.5+ 世界中只有某些输出是可能的:

  1. 读取:0, 0
  2. 阅读:0、5
  3. 读取:5, 0 <- 不可能
  4. 阅读:5、5

输出 3 是不可能的,因为从 volatile _volN 读取“5”会在 2 个线程之间建立一个同步点,这意味着在分配给 _volN 之前从 t1 执行的所有操作必须对 t2 可见。

进一步阅读:

于 2011-01-07T18:54:31.247 回答
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;
    }
}

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

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

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

于 2011-01-06T11:33:05.843 回答
5

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

根据Wikipedia,您有:

  • (在所有 Java 版本中)对 volatile 变量的读取和写入都有全局顺序。这意味着访问 volatile 字段的每个线程都将在继续之前读取其当前值,而不是(可能)使用缓存值。(但是,不能保证 volatile 读写与常规读写的相对顺序,这意味着它通常不是有用的线程构造。)
  • (在 Java 5 或更高版本中)易失性读取和写入建立了先发生关系,就像获取和释放互斥锁一样。

所以基本上你所拥有的是通过声明 field volatile,与之交互创建一个“同步点”,之后任何更改都将在其他线程中可见。但在那之后,使用get()orset()是不同步的。Java Spec有更详尽的解释。

于 2011-01-06T11:26:30.707 回答
0

volatile不“提供可见性”。它的唯一作用是防止处理器缓存变量,从而为并发读取和写入提供发生前的关系。它不影响对象的成员,也不提供任何同步 synchronized锁定。

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

于 2011-01-06T11:32:38.820 回答
0

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

于 2011-01-06T12:56:00.623 回答