初始化非最终字段

Joh*_*hnD 14 java multithreading java-memory-model

我目前正在阅读JSR-133(Java内存模型),我无法理解为什么fy可能未初始化(可能看到0).有人可以向我解释一下吗?

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)

Ada*_*ker 9

这被称为"过早出版"效应.

简单来说,允许JVM重新排序程序指令(出于性能原因),如果这样的重新排序没有违反JMM的限制.

您希望代码f = new FinalFieldExample();运行如下:

1.创建FinalFieldExample
2.将3分配给x
3.将4分配给y
4.将创建的对象分配给变量f

但是在提供的代码中,没有任何东西可以阻止JVM从指令重新排序,所以它可以运行如下代码:

1.创建FinalFieldExample
2.将3分配给x
3.将原始的,未完全初始化的对象f
分配给变量4.将4分配给y

如果在单线程环境中重新排序,我们甚至都不会注意到它.这是因为我们期望在我们开始使用它们之前完全创建对象,JVM尊重我们的期望.现在,如果多个线程同时运行此代码,会发生什么?在下一个示例中writer(),Thread1 正在执行方法和Thread2 - 方法reader():

线程1:创建线程1的实例FinalFieldExample
:将3分配给x
线程1:将原始,未完全初始化的对象分配给变量f
线程2:读取f,它不是空的
线程2:读取fx,它是3
线程2:读取fy,它仍为0
线程1:将4分配给y

绝对不好.为了防止JVM执行此操作,我们需要为其提供有关该程序的其他信息.对于此特定示例,有一些方法可以修复内存一致性:

  • 声明yfinal变量.这将导致" 冻结 "效果.简而言之,如果在构造期间没有泄漏对象的引用,最终变量将在您访问它们时始终初始化.
  • 声明fvolatile变量.这将创建" 同步顺序 "并解决问题.简而言之,指令不能在易失性写入和易失性读取之上重新排序.分配给f变量是易失性写入,这意味着new FinalFieldExample()指令在分配后不能重新排序和执行.从f变量读取是一个易失性读取,因此f.x在它之前不能执行读取.v-write和v-read的组合称为同步顺序,并提供所需的内存一致性.

是一个很好的博客,可以回答你关于JMM的所有问题.


Evg*_*eev -1

Java 内存模型允许线程在初始化 non-final 之前创建一个FinalFieldExample、初始化 Finalx并保存对 FinalFieldExample 实例的引用到字段。fy