1

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

图 4 给出了一个示例,展示了最终字段与正常字段的比较。FinalFieldExample 类有一个最终的 int 字段 x 和一个非最终的 int 字段 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
      }
    }
}

我的困惑是对象'Obj'具有最终和非最终字段已完全初始化并被线程'T'引用,T只会看到最终字段的正确值?构造后未发生突变的非最终字段呢?我知道如果它们在构造线程'T'之后发生突变,可能看不到新值(除非该字段是 volatile )。但是,如果该字段是非最终的和非易失的并且在构造后没有发生突变,我该怎么办?

JVM 如何实现与 'final' 相关的保证?例如,对于 volatile 存在内存屏障。

4

3 回答 3

6

这在这个答案中得到了解决:

这是对象的安全发布吗?

去引用:

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

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

这是Java 语言定义 (17.4)的一部分。有关final字段的详细信息也在JLS (17.5)中。

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

要使此代码正确,您也必须使其f正确volatile

于 2013-11-13T23:50:32.373 回答
3

JVM 如何实现与 'final' 相关的保证?例如,对于 volatile 存在内存屏障。

为了尊重final字段的语义,不能进行一些重新排序,并且可能需要一些内存屏障(在某些处理器上)。见http://g.oswego.edu/dl/jmm/cookbook.html

这意味着final不是免费的午餐。final在我们到处使用之前考虑到这一点。(仅仅因为它在约书亚的书中并不意味着它是正确的)

于 2013-11-14T02:27:32.340 回答
1

我的困惑是对象'Obj'具有最终和非最终字段已完全初始化并被线程'T'引用,T只会看到最终字段的正确值?

保证所有线程都能看到正确的final字段值。这没有说明非最终字段。

构造后未发生突变的非最终字段呢?

修饰符仅适用于final特定字段。您可能会在其他非最终字段中“走运”,但保证仅适用标记为 的字段final

在构造函数中设置非最终字段并且以后不修改的事实是无关紧要的。

于 2013-11-13T23:47:25.090 回答