Ваша идиома безопасна тогда и только тогда , когда ссылка на HashMap
это безопасно опубликована . Безопасная публикация имеет дело не с чем-либо, что связано с ее внутренним устройством HashMap
, с тем, как создаваемый поток делает ссылку на карту видимой для других потоков.
По сути, единственная возможная гонка здесь - между построением HashMap
и любыми потоками чтения, которые могут получить к нему доступ до того, как он будет полностью построен. Большая часть обсуждения касается того, что происходит с состоянием объекта карты, но это не имеет значения, поскольку вы никогда не изменяете его, поэтому единственная интересная часть - это то, как HashMap
публикуется ссылка.
Например, представьте, что вы публикуете карту следующим образом:
class SomeClass {
public static HashMap<Object, Object> MAP;
public synchronized static setMap(HashMap<Object, Object> m) {
MAP = m;
}
}
... и в какой-то момент setMap()
вызывается с картой, и другие потоки используют SomeClass.MAP
для доступа к карте и проверяют наличие null следующим образом:
HashMap<Object,Object> map = SomeClass.MAP;
if (map != null) {
.. use the map
} else {
.. some default behavior
}
Это небезопасно, даже если кажется, что это так. Проблема не в том , что нет случается, перед тем отношения между множествомSomeObject.MAP
и последующим чтением в другом потоке , поэтому поток чтения может видеть частично построенную карту. Он может делать что угодно, и даже на практике он может помещать поток чтения в бесконечный цикл .
Чтобы безопасно опубликовать карту, вам необходимо установить происходит, прежде , чем отношения между написанием ссылки на HashMap
(т. Е. Публикацией ) и последующими читателями этой ссылки (т. Е. Потреблением). Удобно, что есть только несколько легко запоминающихся способов сделать это [1] :
- Обмен ссылками через правильно заблокированное поле ( JLS 17.4.5 )
- Используйте статический инициализатор для инициализации хранилищ ( JLS 12.4 )
- Обмен ссылками через изменчивое поле ( JLS 17.4.5 ) или, как следствие этого правила, через классы AtomicX
- Инициализируйте значение в конечном поле ( JLS 17.5 ).
Наиболее интересными для вашего сценария являются (2), (3) и (4). В частности, (3) применяется непосредственно к приведенному выше коду: если вы преобразуете объявлениеMAP
в:
public static volatile HashMap<Object, Object> MAP;
тогда все кошерно: читатели, которые видят ненулевое значение, обязательно имеют раньше» с магазином MAP
и, следовательно, видят все магазины, связанные с инициализацией карты.
Другие методы изменяют семантику вашего метода, поскольку оба (2) (с использованием статического инициализатора) и (4) (с использованием final ) подразумевают, что вы не можете устанавливать MAP
динамически во время выполнения. Если тебе не нужно , просто объявитеMAP
как a, static final HashMap<>
и безопасная публикация вам гарантирована.
На практике правила просты для безопасного доступа к «неизменяемым объектам»:
Если вы публикуете объект, который не является неизменяемым по своей сути (как во всех объявленных поляхfinal
) и:
- Вы уже можете создать объект, который будет назначен в момент объявления a : просто используйте
final
поле (в том числе static final
для статических членов).
- Вы хотите назначить объект позже, когда ссылка уже будет видна: используйте изменчивое поле b .
Это оно!
На практике это очень эффективно. Использование static final
поля, например, позволяет JVM предполагать, что значение не меняется в течение всего срока службы программы, и сильно оптимизировать его. Использование поля- final
члена позволяет большинству архитектур читать поле способом, эквивалентным нормальному чтению поля, и не препятствует дальнейшей оптимизации. c .
Наконец, использование volatile
действительно имеет некоторое влияние: аппаратный барьер не требуется на многих архитектурах (например, x86, особенно на тех, которые не разрешают чтение для передачи чтения), но некоторая оптимизация и переупорядочение могут не происходить во время компиляции - но это эффект вообще небольшой. Взамен вы действительно получаете больше, чем просили - вы можете не только безопасно опубликовать карту HashMap
, но и сохранить столько немодифицированных HashMap
файлов, сколько захотите, в той же ссылке и быть уверенными, что все читатели увидят безопасно опубликованную карту. .
Для получения дополнительных сведений см. Шипилев или этот FAQ Мэнсона и Гетца .
[1] Прямая цитата из Шипилева .
а Это звучит сложно, но я имею в виду, что вы можете назначить ссылку во время создания - либо в точке объявления, либо в конструкторе (поля-члены), либо в статическом инициализаторе (статические поля).
b При желании вы можете использовать synchronized
метод для получения / установки илиAtomicReference
или что-то еще, но мы говорим о минимальной работе, которую вы можете выполнить.
c В некоторых архитектурах с очень слабыми моделями памяти (я смотрю на вас , Alpha) может потребоваться какой-то тип барьера чтения перед final
чтением, но сегодня это очень редко.