Java 对可变对象使用volatile关键字

Java 对可变对象使用volatile关键字,java,concurrency,volatile,mutable,Java,Concurrency,Volatile,Mutable,在Java中,我知道volatile关键字提供了变量的可见性。问题是,如果变量是对可变对象的引用,volatile是否也为该对象内的成员提供可见性 在下面的示例中,如果多个线程正在访问volatile Mutable m并更改值,它是否正常工作 范例 class Mutable { private int value; public int get() { return a; } public int set(int value)

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

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

范例

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

class Test {
    public volatile Mutable m;
}

volatile
仅提供对声明为volatile的对象的引用的保证。该实例的成员无法同步

根据报告,您有:

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

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

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


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

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

它不能保证关于get()的任何内容

因此,使用以下顺序:

Thread-1: get()     returns 2
Thread-2: set(3)
Thread-1: get()    
你完全可以得到2而不是3<代码>易失性不会对此进行任何更改

但是如果您将
Mutable
类更改为:

class Mutable {
    private volatile int value;
    public int get()
    {
        return a;
    }
    public int set(int value)
    {
        this.value = value;
    }
}
然后保证来自线程1的第二个
get()
将返回3

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


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

使用
volatile
而不是完全
同步的
值本质上是一种优化。与同步访问相比,
volatile
值提供的保证较弱,这是优化的原因。过早优化是万恶之源;在这种情况下,邪恶可能很难被追踪,因为它将以种族条件等形式存在。因此,如果您需要询问,您可能不应该使用它。

这是关于volatile的一些细节的旁注解释。写这篇文章是因为评论太多了。我想给出一些例子来说明volatile是如何影响可见性的,以及它在JDK1.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之前的世界中,易失性访问和非易失性访问之间没有定义的关系。因此,该程序的输出可以是:

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

  • 读:0,0
  • 读:0,5

  • 阅读:5,0为了进行比较,您可能想看看AtomicReference,它可以正确地执行此操作,并且是内置的。如果您只阅读
    Test.m
    ,则在建立关系之前根本不会发生任何情况。这是不正确的。您忽略了wikipedia中的第二点,java 5使volatile影响非volatile变量。@jtahlborn您是对的,我添加了第二点并重新编写了我自己的解释。您的解释仍然不正确。这似乎仍然暗示volatile不会影响非volatile字段。在m的第一次赋值时,保证了值的可见性。更多细节请参见我在@Gugusee的帖子上的评论。实际上,volatile可能会影响对象的成员(而不是OP如何使用它)。“在关系提供可见性保证之前发生”。@jtahlborn-您能演示一下如何做到这一点吗?关于第二点;结果是写操作对读操作是可见的,但术语“提供变量的可见性”太模糊而没有意义。@OrangeDog:假设您创建了一个可变的本地实例,并用3个不同的值调用了set()方法,然后将本地可变的实例赋给了m。m的所有后续读取都将保证看到第一个线程在分配m之前设置的“最后”值。因此,当分配m时,值的可见性得到保证。但是,正如其他地方正确指出的那样,在赋值给m之后对set()的任何未来调用的影响都不能保证是可见的。(其中“可见”表示保证被另一个线程看到)。@jtahlborn-这描述了一种情况,即只影响对象变量(m),而不影响其成员。@OrangeDog: