Java内存模型和最终字段

sne*_*egi 1 java multithreading

当我向JMM提出与“最终”相关的担保时,我最近感到困惑。这是JMM的摘录和示例

图4给出了一个示例,该示例演示了最终场与正常场的比较。类FinalFieldExample有一个最终整数字段x和一个非最终整数y。一个线程可以执行方法writer(),而另一个线程可以执行方法reader()。因为writer()在对象的构造函数完成之后写入f,所以将保证reader()可以看到fx的正确初始化值:它将读取值3。因此,不能保证reader()方法的值是4

class FinalFieldExample {
    final int x;
    int y;
    static FinalFieldExample f;

    public FinalFieldExample() {
      x = 3;
      y = 4;
    }

    static void writer() {
      f = new FinalFieldExample();
    }

    static void reader() {
      if (f != null) {
        int i = f.x; // guaranteed to see 3
        int j = f.y; // could see 0
      }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的困惑是,对象'Obj'具有final和non-final字段是否已完全初始化并由线程'T'引用,T将仅看到final字段的正确值吗?构造后不会发生突变的非最终字段呢?我了解,如果在构造线程“ T”之后对其进行了突变,则可能看不到新值(除非该字段为volatile)。但是,如果该领域是非最终性和非易失性的,并且在构造之后没有发生变化,我该怎么办?

JVM如何实现与“最终”相关联的保证?例如,对于易失性,存在存储障碍。

Gra*_*ray 5

此问题已解决:

这是对象的安全出版物吗?

报价:

问题围绕指令的优化和重新排序。当您有两个正在使用构造对象且没有同步的线程时,出于效率考虑,编译器可能会决定对指令重新排序,并为对象分配内存空间,然后在构造函数和构造函数完成之前将其引用存储在item字段中。字段初始化。或者,它可以重新排序内存同步,以便其他线程以这种方式感知它。

如果将字段标记为final,它将强制编译器在构造函数完成之前完成对该字段的初始化。非最终字段没有此类保证。

这是Java语言定义(17.4)的一部分。有关final字段的详细信息,请参见JLS(17.5)

更具体地说,该writer()方法构造的实例FinalFieldExample并将其存储在static字段中以供其他线程使用。由于指令重新排序,该y字段可能尚未初始化。如果同一线程调用,reader()它将被y视为,4但其他线程可能会将其视为,0因为初始化和发布之前f可能已被设置和使用。 y

为了使这个代码是正确的,你必须fvolatile为好。

  • @snegi如果`f`是易失的,则对`y`的写操作不能在对`f`的写操作后重新排序。 (3认同)