JMM保证最终为对象的字段和非最终引用

gst*_*low 4 java concurrency visibility final java-memory-model

我尝试理解最终字段的语义.

让研究代码:

public class App {

    final int[] data;
    static App instance;

    public App() {
        this.data = new int[]{1, 0};
        this.data[1] = 2;
    }


    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                instance = new App();
            }
        }).start();

        while (instance == null) {/*NOP*/}
        System.out.println(Arrays.toString(instance.data));
    }
}
Run Code Online (Sandbox Code Playgroud)

我有一些问题:

  1. jmm是否保证,如果应用程序终止,那么它输出[1,2]?
  2. jmm是否保证在循环终止后instance.data不为null?

PS我不知道如何使标题正确,随时编辑.

额外

如果我们更换,是否有可见性差异:

public App() {
    this.data = new int[]{1, 0};
    this.data[1] = 2;
}
Run Code Online (Sandbox Code Playgroud)

public App() {
    int [] data = new int[]{1, 0};
    data[1] = 2;
    this.data = data;    
}
Run Code Online (Sandbox Code Playgroud)

另外我想知道wjat将final 在我的例子中替换为volatile.

因此,我想得到关于4个新病例的解释

Hol*_*ger 6

是的,如果应用程序永远终止,它将输出[1,2].关键是final字段语义作为一个整体应用于构造函数,将数组引用写入字段时的确切时间是无关紧要的.这也意味着在构造函数中,重新排序是可能的,因此如果this引用在构造函数完成之前转义,则所有保证都是无效的,无论是this在程序顺序中写入之前还是之后转义.由于在您的代码中,this在构造函数完成之前不会转义,因此保证适用.

参考JLS§17.5.,final字段语义:

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

请注意,它指的是完全初始化的状态,而不是对特定final字段的写入.这也将在下一节§17.5.1中讨论:

o为对象,co的构造函数,其中写入final字段f.当c退出时,正常或突然发生对ofinalf的冻结动作.


如果将变量更改为volatile,则几乎不提供任何保证.一个volatile领域建立之前发生该变量写入和后续读取之间的关系,但往往忽略了关键的一点是词" 后续 ".如果App实例未正确发布,就像在您的示例中一样,则不保证主线程的读取instance.data将是后续的.如果它读取null现在可能的引用,那么您知道它不是后续的.如果它读取非null引用,则表示它是在字段写入之后,这意味着您可以保证1在第一个插槽中读取,但是对于第二个插槽,您可能会读取02.

如果你想在屏障和重新排序方面讨论这个问题,volatile写入data保证所有先前的写入都被提交,包括写入1第一个数组插槽,但它不保证后续的非volatile写入不会提前.因此,仍然可能Appvolatile写入之前执行不正确的引用发布(尽管很少发生).

如果将写入移动到构造函数的末尾,则一旦null看到非数组引用,所有先前的写入都是可见的.对于final字段,它不需要进一步讨论,如上所述,无论如何,写入在构造函数中的实际位置是无关紧要的.对于这种volatile情况,如上所述,您不能保证读取非null引用,但是当您读取它时,所有先前的写入都将被提交.知道表达式new int[]{1, 0};被编译为等价的方法可能会有所帮助hiddenVariable=new int[2]; hiddenVariable[0]=1; hiddenVariable[1]=0;.在构造之后但在将volatile数组引用写入字段之前放置另一个数组,不会改变语义.

  • @Eugene:我通常避免根据这些障碍来解释 JMM,因为它们根本没有出现在规范中。“JSR-133 Cookbook” 对于那些计划编写编译器或优化器的人来说非常有趣,但对于应用程序开发人员来说却不是那么有趣。我对 Java 9 可能带来的变化很好奇,与它一样,显式栅栏成为 API 的一部分。既然这应该反映在规范中,它可能会影响,以后如何解释…… (2认同)