Ваша идиома безопасна тогда и только тогда , когда ссылка на 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чтением, но сегодня это очень редко.