编程语言
首页 > 编程语言> > java – JMM保证最终为对象的字段和非最终引用

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

作者:互联网

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

让研究代码:

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));
    }
}

我有一些问题:

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

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

额外

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

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

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

我也想知道wjat将在我的例子中用volatile替换final.

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

解决方法:

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

参见JLS §17.5., final Field Semantics

An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object’s final fields.

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

Let o be an object, and c be a constructor for o in which a final field f is written. A freeze action on final field f of o takes place when c exits, either normally or abruptly.

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

如果你想在屏障和重新排序方面讨论这个问题,那么对数据的易失性写入保证了所有先前的写操作都被提交,其中包括向第一个数组插槽写入1,但它不保证后续的非易失性写操作没有早点提交.因此,仍然有可能在易失性写入之前执行App引用的不正确发布(尽管很少发生).

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

标签:java,visibility,concurrency,final,java-memory-model
来源: https://codeday.me/bug/20190527/1164775.html