I have a question regarding the Java Memory Model. Here is a simple class presenting the problem:
public class Immutable
In this example, everything will be fine (hmm, let's suspend judgement a bit). Immutability is ambrosia when it comes to thread-safety - if a value cannot change, the majority of concurrency problems are immediately no longer a concern.
Amir mentioned volatile
which is generally useful - but the constructor also has similar semantics for final
variables that ensure visibility. See JLS clause 17.5 for details - essentially the constructor forms a happens-before relationship between the write to the final variables and any subsequent reads.
EDIT: So you set the values reference to the array in the constructor, it's visible across all threads at that point, and then it doesn't change. So we know all other threads will see the same array. But what about the array's contents?
As it stands, array elements don't have any special semantics with regard to volatility, they're as if you just declared a class yourself something like:
public class ArrayTen {
private int _0;
private int _1;
// ...
private int _9;
public int get(int index) {
if (index == 0) return _0;
// etc.
}
}
So - another thread will only see these variables if we can do something to establish the happens-before relationship. And if my understanding is correct this requires but a small change to your original code.
We already know that the setting of the array reference happens-before the end of the constructor. An additional point which is always true, is that actions in one thread happen-before later actions in that same thread. So we can combine these by setting the array fields first, and then assigning the final field, so as to get this transitive guarantee of visibility. This will of course require a temporary variable:
public class ImmutableIntArray {
private final int[] array;
public ImmutableIntArray() {
int[] tmp = new int[10];
for (int i = 0; i < 10; i++) {
tmp[i] = i;
}
array = tmp;
}
// get() etc.
}
I think this is guaranteed to be safe, now that we've switched the seemingly irrelevant order of assignment and population.
But again, there might be something else I've missed which means the concurrency guarantees aren't as robust as hoped. This question is to my mind an excellent example of why writing bulletproof multithreaded code is tricky, even when you think you're doing something very simple, and how it takes a lot of thought and caution (and then bugfixes) to get right.