【问题标题】:Java Memory Model and final fields [duplicate]Java内存模型和最终字段[重复]
【发布时间】:2013-11-13 23:34:28
【问题描述】:

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

图 4 给出了一个示例,展示了最终字段与正常字段的比较。班上 FinalFieldExample 有一个最终的 int 字段 x 和一个非最终的 int 字段 y。一个线程可能执行方法 writer(),而另一个线程可能执行方法 reader()。因为 writer() 写 f 在对象的构造函数完成后,reader() 将保证看到 f.x 的正确初始化值:它将读取值 3。但是,f.y 不是最终的;因此,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 存在内存障碍。

【问题讨论】:

标签: java multithreading


【解决方案1】:

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

Is this a safe publication of object?

引用:

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

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

这是Java language definition (17.4) 的一部分。 final 字段的详细信息也在JLS (17.5) 中。

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

要使此代码正确,您还必须将f 设为volatile

【讨论】:

  • 灰色。谢谢您的回复。虽然编译器重新排序指令(字节码指令,而不是程序指令)解释了为什么 f.y 不会返回 4。JMM 说:“因为 writer() 在对象的构造函数完成后写入 f”。所以在我看来,规范似乎排除了这种重新排序。
  • 添加了更多细节@snegi。构造函数可以完成,f 字段集,其他线程可以读取f.y然后它可以被初始化。或者初始化可能尚未发布。
  • 但是如果构造函数完成了 f.y 字段已经被赋值了,对吧?
  • 没有。这就是重新排序@snegi 的指令的重点。 构造函数完成之后可以分配非最终字段。
  • @snegi 如果f 是易失性的,则写入y 无法在写入f 后重新排序
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    保证所有线程都能看到final 字段的正确值。这与非最终字段无关。

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

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

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

    【讨论】:

    • 在实践中,类中至少有一个 final 字段似乎足以使所有其他字段可见。 StoreStore 屏障同步了所有内容,并且 JIT 编译器不会在构造函数边界上重新排序代码。依赖这种行为绝对不是一个好主意。
    猜你喜欢
    • 1970-01-01
    • 2016-02-20
    • 1970-01-01
    • 1970-01-01
    • 2014-06-10
    • 1970-01-01
    • 2020-10-25
    • 2015-08-12
    • 2019-07-25
    相关资源
    最近更新 更多