将 volatile 关键字与可变对象结合使用

2022-09-01 09:58:15

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

在下面的示例中,如果多个线程正在访问和更改 ?volatile Mutable mvalue

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

class Test {
    public volatile Mutable m;
}

答案 1

这是对波动性一些细节的旁注解释。在这里写这个,因为它太不适合评论了。我想举一些例子来说明易失性如何影响可见性,以及它在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+ 世界中,易失性访问的语义发生了变化,因此易失性访问以与同步完全相同的方式影响非易失性访问。因此,在 jdk1.5+ 世界中只能使用某些输出:

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

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

延伸阅读:


答案 2

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

它不能保证你的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;
    }
}

然后保证线程 1 中的第二个将返回 3。get()

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

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


推荐