The racy single-check idiom is a technique for lazy initialization without synchronization or volatile
. It can be used when initialization is allowed to be performed concurrently by multiple threads, but you don’t care as long as the result is consistent. The JDK itself does this e.g. in ConcurrentHashMap.keySet
(JDK 17):
public KeySetView<K,V> keySet() {
KeySetView<K,V> ks;
if ((ks = keySet) != null) return ks;
return keySet = new KeySetView<K,V>(this, null);
}
This technique only works with primitive values or immutable classes, where every field has to be final to ensure safe publication. Here is an article in German that explains it in depth.
Now we had a discussion whether it is safe to use this technique when you have an intermediate result of the same type during initialization. For example:
class SumUpToTen {
private Integer result;
int getResult() {
Integer res = result;
if (res != null)
return res;
int sum = 0;
for (int i = 1; i <= 10; i++)
sum += i;
result = sum;
return sum;
}
}
Here, we are using the racy single-check idiom to initialize the value for the sum of all integers from 1 to 10. This is of course a silly thing to do, but it’s an example where in the initialization case, we have several intermediate results of the same type as the final result.
Now we know that in the absence of synchronization and volatile
, the compiler can reorder instructions as long as the single-threaded behavior stays the same.
The question is:
Can it assign intermediate results to the instance field, i.e. replacing the local variable sum
with accesses to the field result
?
On the one hand, it’s true that if it would do that, the single-threaded behavior would stay the same.
On the other hand, why should it? Assigning a value to a field is slower than to a local variable, so there is no benefit in assigning intermediate results. Second, this wouldn’t merely be a reordering of instructions, but a replacement of accesses to local variables to accesses to instance fields. Also, if the safety of the pattern would depend on such minor details, then I think the pattern would be considered way too fragile and no one would use it.
Therefore, I don’t think the compiler can assign intermediate results, and I do think the pattern is safe in this case, but I wanted to double check.
Also, I would normally extract the computation of the value in its own private method, like computeResult
, but I didn’t do it in the example to better show the “potential unsafety”. I am pretty confident that whether you do extract that or you don’t doesn’t affect the safety of this pattern either, but can you confirm that as well?