在将volatile参考设置为引用新创建的对象后,线程是否仍然将构造函数的效果视为发生?

时间:2017-07-24 11:20:04

标签: java multithreading

我读了here -

  

当线程A写入volatile变量并随后写入线程B.   读取相同的变量,即所有变量的值   在写入volatile变量之前,A可见   读取volatile变量后到B。所以从内存可见性来看   透视,写一个volatile变量就像退出一样   synchronized块和读取volatile变量就像进入一个   同步块

以下代码段取自here,文章的历史可以追溯到2001年,当时volatile关键字的语义不同。

class SomeClass {
  private Resource resource = null;
  public Resource getResource() {
    if (resource == null) {
      synchronized {
        if (resource == null) 
          resource = new Resource();
      }
    }
    return resource;
  }
}

如果引用是易失性的,则双重检查锁定是固定的。

private volatile Resource resource = null;

但我是否需要将Resource类的成员字段设置为volatile 以确保线程安全?

编辑:

提交人在同一篇文章中提及 - 挥发并不意味着你的想法,

  

通常建议的非修复是声明资源字段   SomeClass为volatile。但是,虽然JMM阻止写入   不稳定变量相互重新排序   并确保它们立即被冲洗到主存储器中,它仍然存在   允许读取和写入要重新排序的volatile变量   关于非易失性读写。这意味着 - 除非所有   资源字段也是易变的 - 线程B仍然可以感知   资源设置为后发生的构造函数的效果   引用新创建的资源。

这意味着考虑到 Double Checked Locking在JDK 5之前就没问题了 资源字段也是易变的,或者类本身是不可变的。 请建议。

1 个答案:

答案 0 :(得分:3)

在一些可追溯到2001年的旧JVM中,volatile关键字的含义有时会被误解,因此实现并没有像它应该的那样运作 - 这就是声明背后的原因在您的问题的第二个引文中,来自2001年的文章 - 易失性是对DCL的非修复。

引自:http://www.javamex.com/tutorials/synchronization_volatile_java_5.shtml

  

从Java 5开始,访问volatile变量会产生内存障碍:   它有效地将所有缓存的变量副本与main同步   内存,就像进入或退出同步块一样   在给定对象上同步。一般来说,这并不大   对程序员的影响,虽然偶尔会造成波动   安全对象发布的良好选择。臭名昭着的双重检查   如果引用,锁定反模式实际上在Java 5中变为有效   声明是不稳定的。

使用Java 5时,事情发生了变化,对volatile字段的写入/读取无法通过非易失性读/写重新排序,因此代码中的所有内容都会在写入volatile之前发生在写之前。根据您的代码段:如果Resource类的成员字段是不可变的,您不需要使它们volatile使其安全,可以被其他线程读取。如果其他线程(构造和初始化Resource实例的字段除外)可以修改这些memeber字段,那么你需要使它们线程安全(例如将它们标记为volatile - 这只是简单的内存屏障的例子,可能还不够)。考虑使用volatile更改的示例:

class SomeClass {
  private volatile Resource resource = null;
  public Resource getResource() {
    if (resource == null) {
      synchronized {
        if (resource == null) 
          resource = new Resource();
      }
    }
    return resource;
  }
}

在将实例分配给volatile字段之前,Resource()构造函数已完全执行。读取线程将看到创建实例的线程所写的所有内存 - 因此所有初始化都是可见的,这意味着resource实例的发布是线程安全的。

要明确:

  

@BrianGoetz在2001年发表的一篇文章中所作的声明将该字段标记为不稳定......

     

...仍然允许重新排序volatile变量的读写   关于非易失性读取和写入`

     

在现代JVM中不再存在(JVM> = Java5)

<强>声明 当我们谈论同步时,我们经常使用术语内存berrier ,但许多消息来源指出,实际上这些屏障阻止了指令的重新排序,因此典型的内存屏障只是确保代码中所有在内存屏障点之前执行的内容(例如,输入synchronized块,编写volatile变量)确实在程序到达内存屏障之前执行(对于例如,你知道如果你读取对volatile变量的引用,它就不会在构造函数真正完成并返回表单初始化之前发布。有很多没有冲到主内存进行同步 - CPU缓存在其他情况下被写入主内存,使内存对多核CPU中的其他线程可见的是缓存一致性硬件