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

Hon*_*gbo 30 java concurrency mutable volatile

在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;
}
Run Code Online (Sandbox Code Playgroud)

jta*_*orn 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();
  }
}
Run Code Online (Sandbox Code Playgroud)

的该测试代码的行为在JDK1.5 +被很好地定义,但明确定义的预JDK1.5.

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

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

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

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

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

进一步阅读:

  • @OrangeDog - 他们是谷歌的热门点击,所以他们很容易找到.我的观点是表明挥发性绝对会影响能见度.我试图展示它是如何改变的一个例子,证明它影响了可见性.这些链接还表明易变性如何影响可见性.因此,你的答案仍然充其量是误导,最坏的情况是错误的.(我实际上并不关心你对同步这个词的使用,因为那是你答案中的正确陈述). (2认同)

Gug*_*see 8

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

它不保证你的get().

所以使用以下顺序:

Thread-1: get()     returns 2
Thread-2: set(3)
Thread-1: get()    
Run Code Online (Sandbox Code Playgroud)

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

但如果你改变你的Mutable课程:

class Mutable {
    private volatile int value;
    public int get()
    {
        return a;
    }
    public int set(int value)
    {
        this.value = value;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • @jtahlborn,你能提供一个参考吗?我在JLS找不到它.它只是说"在对该字段的每次后续读取之前发生对易失性字段的写入(第8.3.1.4节)",但这并不意味着关于字段引用的对象的实际初始化的任何内容. (2认同)

Mar*_*o F 5

volatile仅提供有关对此声明的Object的引用的保证.该实例的成员不会同步.

根据维基百科,你有:

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

所以基本上你所拥有的是通过声明字段volatile,与它交互创建一个"同步点",之后任何更改都将在其他线程中可见.但在那之后,使用get()set()未经启动.在Java规范有一个更透彻的解释.

  • 这是不正确的.你忽略了维基百科中第二个点,其中java 5使volatile变为非易失性变量. (3认同)