Является ли незанятая ленивая модель загрузки, используемая в Guava, действительно потокобезопасной?
Некоторые внутренние типы 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
.
Ответы
Ответ 1
Я не уверен на 100% (я уверен, что кто-то из нашей команды мог бы ответить на это лучше). Тем не менее, пару мыслей:
- Я не думаю, что мы требуем, чтобы это было (гарантировано) потокобезопасным. Непотопляемые коллекции, такие как
HashMultiset
, расширяют AbstractMultiset
. Тем не менее, ConcurrentHashMultiset
также расширяет AbstractMultiset
и использует его реализацию elementSet()
, поэтому, по-видимому, на самом деле он может быть потокобезопасным.
- Я уверен, что безопасность потока этого метода зависит от реализации
createElementSet()
. Из того, что я могу сказать, если Set
, созданный createElementSet()
, неизменен (в том, что поля, которые назначаются при его построении, final
), он должен быть потокобезопасным. Это, по-видимому, верно в случае ConcurrentHashMultiset
по крайней мере.
Изменить: Я спросил об этом Джереми Мэнсона, и он сказал: "Твоя помощь мне кажется прекрасной. Это не потокобезопасно. Если построенный объект имеет все окончательные поля в правильных местах, вы должны быть в порядке, но я не стал бы полагаться на это случайно (обратите внимание, что многие реализации фактически неизменяемы, а не искренне неизменяемы).
Примечание. Для поточно-безопасных коллекций, таких как ConcurrentHashMultiset
, которые используют этот шаблон, созданные объекты преднамеренно и по-настоящему неизменяемы (хотя если бы AbstractSet
изменилось, это могло бы измениться, как заметил Крис в комментариях).