JMM guarantees about final as field and non final reference to the object
Asked Answered
C

1

7

I try to understand final fields semantic.

Lets research code:

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

I have some questions:

  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?

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

Additional

Is there visibility difference if we replace:

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

with

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

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

Thus I want to get explanation about 4 new cases

Carlyn answered 31/1, 2017 at 10:34 Comment(0)
P
9

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

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.

Refer to 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.

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:

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.


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.

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).

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.

Philippi answered 1/2, 2017 at 15:14 Comment(15)
the non-escaping + the StoreStore barrier for final fields, that is why the [1,2] output.Inarch
@Holger, I want to know your opinion about stackoverflow.com/q/41983725/2674303Carlyn
@gstackoverflow: I think that question has been answered by John Vint. Generally, you should not confuse object visibility (like private or protected) with thread visibility. When another thread can concurrently invoke the method accessing the private field, you can have a data race.Philippi
@Holger, by mind I think like you say but it contradicted with feeling thus I was not sure. Thanks for prooveCarlyn
@Holger,I am sorry, but I want to reask one more time because all colleagues say different and your quotes doesn't ask me directly. I can treat it as I want.Carlyn
@Philippi oh, sorry, I forgot to add that this question added to the topicCarlyn
Adding another this.data = data; assignment is not only meaningless, it’s simply not allowed, if data is final. surely I mean that inline declaration will be reduced.Carlyn
Right, difference not spotted well enough. Hope it answers it now.Philippi
@Philippi while I like this answer a lot (your wording is absolutely fantastic sometimes), I would like to add that final introduces a StoreStore barrier that does not allow the publishing of the reference to be reordered with any other field Store in the constructor; while the volatile store preceded by any other non-volatile store does not introduce any barrier; that is why the volatile field has no guarantees of re-orderings. Of course, 1+Inarch
@Eugene: I usually avoid explaining the JMM in terms of these barriers, as they do not appear in the specification at all. The “JSR-133 Cookbook” is very interesting to those planning to write a compiler or optimizer, but not so much for an application developer. I’m curious about the changes that might come with Java 9, as with it, explicit fences become part of the API. Since this should be reflected in the specification, it might affect, how to explain it in the future…Philippi
@Philippi exactly my point. fences are part of jdk-9, seems like it's about time to start mentioning those as well.Inarch
@Eugene: as said, I wait for an official specification, I can refer to. You might have noticed that I try carefully to use exactly the same words as the specification when I mean the same, as a lot of these words are heavily overloaded when used colloquially and this is a topic, where it is too easy to create additional confusion…Philippi
@Holger, I think NullPointerException is also a possible outcome. Even if instance == null check returns false and the loop terminates, the JMM does not guarantee that instance.data sees the non-null reference to instance. Guarantees provided for final fields are formulated using Memory Chain partial order: in simplest case a memory-chain edge appears if a read happens to see the result of a write. These memory-chain edges are not transitive with respect to program order, so one read seeing the result of a write does not guarantee that another reed will see the same result.Kanazawa
I tried to summarize my perspective in #62279180Kanazawa
@NikitaTkachenko yes, definitely. It struck me immediately when seeing this old Q&A, even before reading your comment. It seems, I was too much focused on the data field, which the question was about, so I missed the problem with the double-read of the instance field. I’ll update the answer.Philippi

© 2022 - 2024 — McMap. All rights reserved.