JMM保证有关对象的最终字段和非最终引用 [英] JMM guarantees about final as field and non final reference to the object

查看:72
本文介绍了JMM保证有关对象的最终字段和非最终引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I try to understand final fields semantic.

允许研究代码:

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

我有一些问题:

  1. jmm是否保证如果应用程序终止,然后输出[1,2]?
  2. jmm是否保证循环终止后instance.data不为空?
  1. Does jmm guarantee, that if application terminates then it output [1,2] ?
  2. Does jmm guarantee that instance.data not null after loop termination?

PS .我不知道如何正确设置标题,请随时进行编辑.

P.S. I don't know how to make title correct, feel free to edit.

如果我们替换,是否存在可见性差异

Is there visibility difference if we replace:

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

我还想知道,如果在我的示例中将final替换为volatile,将是wjat.

also I want to know wjat will be if replace final with volatile in my example.

因此,我想获得有关4个新案例的解释

Thus I want to get explanation about 4 new cases

推荐答案

是的,有一些问题.您正在循环后重新读取instance变量,并且由于两次读取都是正常的,因此退出循环并不能保证循环后的读取会读取非null引用.

Yes, with some catch. You are re-reading the instance variable after the loop and since both reads are racy, exiting the loop does not guaranty that the read after the loop reads a non-null reference.

由于此问题不是问题的主题,因此请进行以下更改:

Since this issue is not the topic of the question, assume the following change:

App instance;
while((instance=App.instance) == null) {/*NOP*/}
System.out.println(Arrays.toString(instance.data));

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

Then, if application ever terminates, it will output [1,2]. The point is that the final field semantics applies to the constructor as a whole, the exact time, when the array reference is written to the field, is irrelevant. This also implies that within the constructor, reorderings are possible, so if the this reference escapes before the completion of the constructor, all guaranties are void, regardless of whether this escapes before or after the writes in program order. Since in your code, this does not escape before the constructor’s completion, the guaranty applies.

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

Refer to JLS §17.5., final Field Semantics:

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

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.

请注意,它指的是完全初始化状态,而不是写特定的final字段.下一节

Note that it refers to the completely initialized state, not the write to the particular final fields. This is also addressed in the next section, §17.5.1:

o 是一个对象,而 c o 的构造函数,其中的final字段 f 被写入.当 c 正常或突然退出时,对 o final字段 f 进行冻结操作.

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.如果它读取一个null引用(现在可以使用),那么您将知道它不是后续引用.如果它读取的是非null引用,则说明它是在字段写操作之后,这意味着可以保证在第一个插槽中读取了1,但是对于第二个插槽,您可以读取02.


If you change the variable to volatile, you have almost no guarantees at all. A volatile field establishes a happens-before relationship between a write to that variable and a subsequent read, but the often overlooked key point is the word "subsequent". If the App instance is improperly published, like in your example, there is no guaranty that the main thread’s read of instance.data will be subsequent. If it reads a null reference, which is now possible, then you know that it is not subsequent. If it reads a non-null reference, you know that it is subsequent to the field write, which implies that you are guaranteed to read the 1 in the first slot, but for the second you may read 0 or 2.

如果要在障碍和重新排序方面进行讨论,对datavolatile写入可确保所有先前的写入都已提交,其中包括对第一个阵列插槽的1写入,但它不会不能保证以后的非volatile写操作不会更早提交.因此,仍然有可能在volatile写入之前执行了App引用的不正确发布(尽管这种情况很少发生).

If you want to discuss this in terms of barriers and reordering, the volatile write to data guarantees that all previous writes are committed, which includes the write of 1 to the first array slot, but it doesn’t guaranty that subsequent non-volatile writes are not committed earlier. So it is still possible that the improper publication of the App reference is performed before the volatile write (though that rarely happens).

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

If you move the write to the end of the constructor, all previous writes are visible once a non-null array reference is seen. For final fields, it doesn’t need further discussions, as said above, the actual placement of the write within the constructor is irrelevant anyway. For the volatile case, as said above, you are not guaranteed to read a non-null reference, but when you read it, all previous writes are committed. It might be helpful to know that the expression new int[]{1, 0}; gets compiled to the equivalent of hiddenVariable=new int[2]; hiddenVariable[0]=1; hiddenVariable[1]=0; anyway. Placing another array write after its construction but before the volatile write of the array reference to the field, doesn’t change the semantics.

这篇关于JMM保证有关对象的最终字段和非最终引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆