Skip to content
Advertisement

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

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

Advertisement

Answer

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.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement