Mohendra Amatya
Mohendra Amatya

Reputation: 441

Does final field guarantee the field values to be visible in a different thread?

I am using JCStress to test for the final variable. I know that final can be used to make sure that when you construct an object, another thread accessing that object doesn't see that object in a partially constructed state. Now I have a class A.java as such

public class A {
    final int f;

    A() {
        this.f = 42;
    }

}

According to my knowledge, the constructor should be executed as such

A temp = <new>
temp.f = 42
<freeze value>
fv = temp

Now I'm using the below-mentioned test.

@JCStressTest
@State
public class FinalField {
    A a;

    @Actor
    public void writer() {
        a = new A();
    }

    @Actor
    public void reader(I_Result result) {
        A ta = a;
        if (ta != null) {
            result.r1 = ta.f; 
        }
    }

}

Now, why is it that I see the value 0 in my output? My CPU architecture is x86, so reordering stores with loads also doesn't make sense. The output I get is

           0    94,922,153     FORBIDDEN  No default case provided, assume FORBIDDEN                  
          42    48,638,587     ACCEPTABLE  Final value initialized    

Also, one more thing that I found unusual is that when I declare the field a as static. I get only 42 as my output, and why is that?

          42   299,477,390     ACCEPTABLE  Final value initialized      

Upvotes: 3

Views: 118

Answers (3)

Eugene
Eugene

Reputation: 120988

The explanation is really easy. What is the default value of result.r1? What type is r1? It's an int, and a default value for a int is zero. So when this if (ta != null) does not happen, meaning ta is null, your code will do nothing. That "nothing" translates into leaving r1 to its default value - that is (you already know by now) zero. So when ta == null (and implicitly a == null), you leave r1 to be 0, though you do not do that explicitly.

The solution is trivial:

@Actor
public void reader(I_Result result) {
    A ta = a;
    if (ta != null) {
        result.r1 = ta.f;
    } else {
        result.r1 = -1;
    }
}

and:

@JCStressTest
@State
@Outcome(id = "42", expect = Expect.ACCEPTABLE, desc = "42 is OK")
@Outcome(id = "-1", expect = Expect.ACCEPTABLE, desc = "-1 is OK too")

And now your code will never show 0, if you read a to be non-null, you will always read a.f to be 42. As far as your understanding goes, yes, all threads will see 42 once they see a reference to an instance of A - that is a JLS guarantee.

Upvotes: 1

microBeast
microBeast

Reputation: 11

Another answer already explained why you have problems with 0 and static.
But even with these problems fixed it could be difficult to reproduce partial initialization.
So I would recommend you to take a look at the JCStress source code: it contains samples, and one of them (JMMSample_06_Finals) already does what you want.

Upvotes: 1

user
user

Reputation: 11

Now why is it that I see the value 0 in my output ?

These is the case when a == null (and therefore result.r1 remains 0) in your reader() method.

when I declare the field a as static. I get only 42 as my output, and why is that?

You annotated FinalField with @State, therefore JCStress creates a new instance of FinalField for every execution.
If a is an instance field in FinalField, then it is null initially in every execution.
If a is a static field in FinalField, then it is shared across all executions and is null only in the first execution.

Upvotes: 1

Related Questions