Reputation: 137
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:
Upvotes: 2
Views: 210
Reputation: 719336
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.
With the example as written, that is impossible until after the constructor returns. The reference is not published by the constructor; i.e. it is safely published.
Is it possible for second thread observe 1, 0 values of the array arr?
No. Since the object is safely published, this JLS 17.5. guarantee comes into play:
"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."
By applying the rules of JLS 17.5.1, we can see that these guarantees extend to any fully encapsulated objects initialized by the constructor prior to the freeze at the end on the constructor body.
This is also described in more accessible terminology in Goetz et al "Java: Concurrency in Action".
If you were to change the example to this:
public static class Data {
public static Data instance;
final int[] arr;
public Data() {
instance = this;
arr = new int[]{1, 0};
arr[1] = 2;
}
}
The statement I added changes everything. Now some other thread could see a Data
instance before the constructor completes. It may then be able to see arr[1]
in its intermediate state.
The "leakage" of the the reference to the Data
instance while it it still being constructed is unsafe publication.
Upvotes: 4
Reputation: 18857
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 https://stackoverflow.com/users/1261287/vladimir-sitnikov):
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.")
Upvotes: 1