首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java内存模型中的最终字段

Java内存模型中的最终字段
EN

Stack Overflow用户
提问于 2018-07-16 15:24:33
回答 2查看 420关注 0票数 2

你能解释一下f.y的值是0而不是4吗?这是否是因为其他线程写入将值从4更新为0?此示例取自jls https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5

代码语言:javascript
复制
 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
            } 
        } 
    }
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-07-18 17:58:25

你能解释一下f.y的值是0而不是4吗?

在Java中,编译器/JVM执行的重要优化之一是重新排序指令。只要它不违反语言规范,出于效率原因,编译器就可以自由地重新排序所有指令。在对象构造期间,可以实例化对象、完成构造函数以及在正确初始化对象中的所有字段之前发布其引用。

但是,Java说,如果一个字段被标记为final,那么必须在构造函数完成时正确地初始化它。引用来自您所引用的Java语言规范部分的内容。重点是我的。

当构造函数完成时,对象被认为是完全初始化的。只有在对象被完全初始化后才能看到对对象的引用的线程,保证看到该对象的最终字段的值正确初始化。

因此,当FinalFieldExample被构造并分配给f时,x字段必须被正确初始化为3,但是y字段可能已经被正确初始化,也可能没有被正确初始化。因此,如果thread1调用writer(),然后thread2调用reader(),并将f视为null,则y可能为0(尚未初始化)或4(初始化)。

票数 1
EN

Stack Overflow用户

发布于 2018-07-16 16:34:49

假设我们已经启动了两个线程,如下所示:

代码语言:javascript
复制
new Thread(FinalFieldExample::writer).start(); // Thread #1
new Thread(FinalFieldExample::reader).start(); // Thread #2

我们可以遵循程序的实际操作顺序如下:

  1. Thread #1x = 3
  2. Thread #1f = ...
  3. Thread #2读取f,发现它不是null
  4. Thread #2读取f.x并看到3
  5. Thread #2读取f.y并看到0,因为y似乎还没有编写。
  6. Thread #1y = 4

换句话说,Threads #1#2能够使它们的操作交织在一起,使得Thread #2Thread #1编写操作之前读取f.y

还请注意,对static字段f的写入被允许重新排序,以便似乎发生在写到f.y之前。这只是缺乏任何类型的同步的另一个结果。如果我们将f声明为volatile,则将防止这种重新排序。

关于使用反射写入final字段的注释中有一些讨论,这是正确的。这是在§17.5.3中讨论的。

在某些情况下,例如反序列化,系统将需要在构造后更改对象的final字段。final字段可以通过反射和其他依赖于实现的方法进行更改.

因此,在一般情况下,Thread #2在读取f.x时可以看到任何值。

还有一种更常规的方法可以查看final字段的默认值,只需在赋值之前泄漏this

代码语言:javascript
复制
class Example {
    final int x;

    Example() {
        leak(this);
        x = 5;
    }

    static void leak(Example e) { System.out.println(e.x); }

    public static void main(String[] args) { new Example(); }
}

我认为如果FinalFieldExample的构造函数是这样的话:

代码语言:javascript
复制
static FinalFieldExample f;

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

Thread #2也可以将f.x读入0

这是来自§17.5

当构造函数完成时,对象被认为是完全初始化的。只有在对象被完全初始化后才能看到对对象的引用的线程保证看到该对象的final字段的正确初始化值。

final规范的更多技术部分也包含类似的措辞。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51365246

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档