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
.