Некоторые внутренние типы Guava, такие как AbstractMultiset
, имеют такой шаблон:
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();
}
Идея состоит в том, чтобы отложить создание представлений коллекции (elementSet()
, entrySet()
), пока они не понадобятся. Там нет блокировки вокруг процесса, потому что, если два потока звонят elementSet()
в одно и то же время, вполне можно вернуть два разных значения. Будет полезна запись поля elementSet
, но поскольку записи в ссылочные поля всегда являются атомарными в Java, неважно, кто победит в гонке.
Тем не менее, я беспокоюсь о том, что говорит модель памяти Java о inlining здесь. Если конструкторы createElementSet()
и elementSet
становятся вложенными, кажется, что мы могли бы получить что-то вроде этого:
@Override
public Set<E> elementSet() {
Set<E> result = elementSet;
if (result == null) {
elementSet = result = (allocate an ElementSet);
(run ElementSet constructor);
}
return result;
}
Это позволит другому потоку наблюдать непустое, но не полностью инициализированное значение для elementSet
. Есть ли причина, которая не может произойти? Из моего чтения JLS 17.5 кажется, что другие потоки гарантируют только правильные значения для полей final
в elementSet
, но поскольку elementSet
в конечном итоге происходит от AbstractSet
, я не думаю, что есть гарантия, что все его поля final
.