Some Guava internal types, like AbstractMultiset, have a pattern like this:
private transient Set<E> elementSet;
@Override
public Set<E> elementSet() {
Set<E> result = elementSet;
if (result == null) {
elementSet = result = createElementSet();
}
return result;
}
Set<E> createElementSet() {
return new ElementSet();
}
The idea is to delay creating the collection views (elementSet(), entrySet()) until they're actually needed. There's no locking around the process because if two threads call elementSet() at the same time, it's okay to return two different values. There will be a race to write the elementSet field, but since writes to reference fields are always atomic in Java, it doesn't matter who wins the race.
However, I worry about what the Java memory model says about inlining here. If createElementSet() and ElementSet's constructor both get inlined, it seems like we could get something like this:
@Override
public Set<E> elementSet() {
Set<E> result = elementSet;
if (result == null) {
elementSet = result = (allocate an ElementSet);
(run ElementSet's constructor);
}
return result;
}
This would allow another thread to observe a non-null, but incompletely initialized value for elementSet. Is there a reason that can't happen? From my reading of JLS 17.5, it seems like other threads are only guaranteed to see correct values for final fields in elementSet, but since ElementSet ultimately derives from AbstractSet, I don't think there's a guarantee that all its fields are final.