I have a class with
private volatile long[][] data = new long[SIZE][];
which initially contains just nulls and a method which accesses it. When it hits an null element, it creates a long[]
and stores for future use. This operation is idempotent and multiple threads wasting time on the same element is not a problem.
No thread must ever see an incompletely filled element. I hope that the following code does it right:
long[] getOrMakeNewElement(int index) {
long[] element = data[index]; // volatile read
if (element==null) {
element = makeNewElement(index); // local operation
data = data; // ugliness 1
data[index] = element;
data = data; // ugliness 2
}
return element;
}
The first ugliness is for ensuring that other threads can in theory see the changes made to element
. They can't actually access it as it isn't stored anyway yet. However, in the next like element
gets stored and another thread may or may not see this store, so AFAIK the first ugliness is necessary.
The second ugliness then just ensures that other threads see the new data
containing element
. The strange thing here is using the ugliness twice.
Is this is necessary and sufficient for safe publication?
Note: While this question is similar to this one, it's no duplicate as it deals with modifying an existing 1D array rather than creating one. This makes the answer clear.
Update
Note: This is no production code and I know and don't care about the alternatives (AtomicReferenceArray
, synchronized
, whatever, ...). I wrote this question in order to learn more about the JMM. It's a real code, but used just for my fooling around with project Euler and no animals were harmed in the process.
I guess, a safe and practical solution would be
class Element {
Element(int index) {
value = makeNewElementValue(index);
}
final long[] value;
}
private volatile Element[] data = new Element[SIZE];
where the Element
ensures visibility via the Semantics of final Fields.
As pointed by user2357112, there's also a (IMHO harmless) data race when multiple thread write the same data[index]
, which is actually easy to avoid. While the reading must be fast, making a new element is slow enough to allow for any synchronization needed. It'll also allow a more efficient initialization of the data.