INlHELL INlHELL - 5 months ago 11
Java Question

Could it be possible to observe an intermediate state of a final array during object creation?

Imagine, that in my concurrent application I have a Java class like this (very simplified):

Updated:

public class Data {
static Data instance;

final int[] arr;

public Data() {
arr = new int[]{1, 0};
arr[1] = 2;
}
public static void main(String[] args) {
new Thread(() -> instance = new Data()).start();
System.out.println(Arrays.toString(instance.arr));
}
}


Incorrect:

public static class Data {
final int[] arr;

public Data() {
arr = new int[]{1, 0};
arr[1] = 2;
}
}


Let's say one thread creates an object of this class and another thread that has a reference to this object reads values from an array
arr
.
Is it possible for second thread observe
1, 0
values of the array
arr
?

To check this case I've written test with JCStress framework (thanks for it to @AlekseyShipilev):

Test seems incorrect as well after comments below

@JCStressTest
@Outcome(id = "2, 1", expect = Expect.ACCEPTABLE, desc = "Seeing the set value.")
@Outcome(expect = Expect.FORBIDDEN, desc = "Other values are forbidden.")
@State
public class FinalArrayTest {

Data data;

public static class Data {
final int[] arr;

public Data() {
arr = new int[]{1, 0};
arr[1] = 2;
}
}

@Actor
public void actor1() {
data = new Data();
}

@Actor
public void actor2(IntResult2 r) {
Data d = this.data;
if (d == null) {
// Pretend we have seen the set value
r.r1 = 2;
r.r2 = 1;
} else {
r.r1 = d.arr[1];
r.r2 = d.arr[0];
}
}
}


On my machine, second thread always observes last assignment
arr[1] = 2
, but I still doubt, will I have the same result on all platforms like ARM?

All tests were executed on computer with this configuration:


  • Number of cores: 4

  • Java vendor: Oracle

  • OS: Linux

  • OS Version: 4.4.7-300.fc23.x86_64

  • Java version: 9-ea+123

  • OS Arch: amd64

  • Number of test iterations: 10^10


Answer

The axiomatic final field semantics is governed by a special happens-before rule. That rule is (a slide from my JMM Pragmatics, but most of the subsequent explanations there are due to http://stackoverflow.com/users/1261287/vladimir-sitnikov):

enter image description here

Now. In the example when array element is modified after the initial store, here is how actions relate to the program:

public class FinalArrayTest {
    Data data;

    public static class Data {
        final int[] arr;

        public Data() {
            arr = new int[]{1, 0};
            arr[1] = 2; // (w)
        } // (F)
    }

    @Actor
    public void actor1() {
        data = new Data(); // (a)
    }

    @Actor
    public void actor2(IntResult1 r) {
        // ignore null pointers for brevity
        Data d = this.data; 
        int[] arr = d.arr; // (r1)
        r.r1 = arr[1]; // (r2) 
    }
}

w hb F and F hb a trivially. a mc r1 (due to a mc read(data) and read(data) dr read(data.arr). Finally, r1 dr r2 because it is a dereference of array element. The construction is complete, and therefore write action arr[1] = 2 happens-before read action r.r1 = arr[1] (reads 2). In other words, this execution mandates seeing "2" in arr[1].

Note: in order to prove that all executions are yielding "2", you have to prove that no execution can read the initial store to array element. In this case, it is almost trivial: there are no executions that can see the array element writes and bypass the freeze action. If there is a this "leakage", such an execution is trivially constructable.

Aside: note that it means that a final field store initialization order is irrelevant for final field guarantees, as long as nothing leaks. (This is what spec alludes to when saying "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.")