Search code examples
javaconcurrencymemory-visibility

When does freeze action happen in java constructor?


I am confused about final field semantics in java. I've read 17.5 paragraph jls and found this example:

class FinalFieldExample { 
    final int x;
    int y; 
    static FinalFieldExample f;

    public FinalFieldExample() {
        x = 3; 
        y = 4; 
    } 

    static void writer() {
        f = new FinalFieldExample();
    } 

    static void reader() {
        if (f != null) {
            int i = f.x;  // guaranteed to see 3  
            int j = f.y;  // could see 0
        } 
    } 
}

The JLS states that there is no guarantee that we will see 4 in reading f.y. And it also says next:

It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are.

I don't understand this. Imagine that we have the following code:

public FinalFieldExample() {
        ar = new int[n]; // ar is final var
        ar[0] = 1;
    } 

Does it mean that we have guarantee that other thread will read array with 1 at first position? I heard yes. And I've read that freeze action happens when we write to a final variable, but why we can read array with correct elements? (I found out it in this article.) Writing to final field happens before writing to array's first element.

And if it is true, why in first example(with x and y) we don't have guarantees to see 4 in y?

And my final question is: Does freeze action happen at the end of constructor(during reference publication) or after each write to a final field?

I've read this article but didn't find example with 1 final and 1 non final writes in constructor. Alexey says that synthetic freeze happens at the end of constructor. Does it mean that we will see correct values in reading from non final variables initialized in constructor? Is writing to final variable enough to see writes to other non final variable?


Solution

  • The JLS states that there is no guarantee that we will see 4 in reading f.y.

    Yes, if one thread invokes FinalFieldExample.reader() and another invokes FinalFieldExample.writer(), with nothing to establish a happens-before relationship between these, then the program is not properly synchronized, and its behavior is undefined. In particular, it is possible for the thread invoking reader() to observe a partially-initialized state of FinalFieldExample.f.

    It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are.

    I don't understand this.

    A field whose type is a reference type -- a class, interface, or array type -- stores a reference to an object (or null), not the object itself. The spec is saying that all writes to the referenced object that happened before the assignment of the final field's value will be visible to any thread that observes the field's (initialized) value.

    Imagine that we have the following code:

    public FinalFieldExample() {
            ar = new int[n]; // ar is final var
            ar[0] = 1;
        } 
    

    Does it mean that we have guarantee that other thread will read array with 1 at first position?

    No. The assignment to ar[0] happens after the assignment to final variable ar. It is not guaranteed that a thread that can observe the object in a partially-initialized state must read 1 from ar[0].

    Does freeze action happen at the end of constructor(during reference publication) or after each write to a final field?

    The term "freeze" in this context refers to visibility guarantees related to the completion of the constructor. That has nothing to do with the fact that the constructor can assign each otherwise uninitialized final field only once, or with the other visibility guarantee you asked about.