Ваша идиома безопасна тогда и только тогда, когда ссылка на HashMap
является безопасно опубликованной . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *} Тогда, тогда как • • • • •
По сути, единственно возможная гонка здесь - это создание HashMap
и любых потоков чтения, которые могут получить к нему доступ до того, как он будет полностью создан. Большая часть обсуждения посвящена тому, что происходит с состоянием объекта карты, но это не имеет значения, поскольку вы никогда не изменяете его - поэтому единственная интересная часть - это то, как публикуется ссылка HashMap
.
Например, представьте, что вы публикуете карту следующим образом:
class SomeClass {
public static HashMap<Object, Object> MAP;
public synchronized static setMap(HashMap<Object, Object> m) {
MAP = m;
}
}
... и в какой-то момент setMap()
вызывается с картой, и другие потоки используют SomeClass.MAP
для доступа к карте и проверяют наличие нуля следующим образом:
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
как static final HashMap<>
и вам гарантированно безопасная публикация.
На практике правила безопасного доступа к «неизмененным объектам» просты:
Если вы публикуете объект, который не является неотъемлемо неизменным (как во всех объявленных полях final
) и:
- Вы уже можете создать объект, который будет назначен в момент объявления a : просто используйте поле
final
(включая static final
для статических элементов).
- Вы хотите назначить объект позже, после того как ссылка уже видна: используйте изменчивое поле b .
Вот и все!
На практике это очень эффективно. Например, использование поля static final
позволяет JVM предполагать, что значение не изменяется в течение всего жизненного цикла программы, и сильно его оптимизировать. Использование final
поля-члена позволяет большинству архитектур считывать поле способом, эквивалентным нормальному чтению поля, и не препятствует дальнейшей оптимизации c .
Наконец, использование volatile
оказывает определенное влияние: во многих архитектурах не требуется аппаратный барьер (например, x86, особенно те, которые не разрешают чтение для прохождения чтения), но некоторая оптимизация и переупорядочение могут не произойти во время компиляции - но этот эффект обычно невелик. Взамен вы фактически получаете больше, чем просили - вы можете не только безопасно опубликовать один HashMap
, вы можете хранить столько же немодифицированных HashMap
, сколько захотите, к одной и той же ссылке и быть уверенными, что все читатели увидит благополучно опубликованную карту.
Более подробная информация приведена в Шипилев или в этом FAQ Мэнсона и Гетца .
[1] Прямое цитирование shipilev .
a Звучит сложно, но я имею в виду, что вы можете назначить ссылку во время построения - либо в точке объявления, либо в конструкторе (поля-члены) или статическом инициализаторе (статические поля).
b При желании вы можете использовать метод synchronized
для получения / установки или AtomicReference
или что-то еще, но мы говорим о минимальной работе, которую вы можете сделать.
c Некоторые архитектуры с очень слабыми моделями памяти (я смотрю на you , Alpha) могут потребовать некоторый тип барьера чтения перед final
чтением - но сегодня это очень редко.