Безопасно ли получать значения из java.util.HashMap из нескольких потоков (без изменений)?

Существует случай, когда карта будет построена, и после ее инициализации она никогда не будет изменена снова. Однако к нему можно будет получить доступ (только через get (key)) из нескольких потоков. Безопасно ли использоватьjava.util.HashMap в этом случае?

(В настоящее время я счастливо используюjava.util.concurrent.ConcurrentHashMapи не имеют никакой необходимости улучшать производительность, но мне просто любопытно, еслиHashMap было бы достаточно. Следовательно, этот вопросnot & quot; Какой я должен использовать? & quot; и это не вопрос производительности. Скорее, вопрос «Будет ли это безопасно?»

 kaqqao13 мая 2016 г., 09:38
@ Heath Borders, если экземпляр a был статически инициализирован неизменяемым HashMap, он должен быть безопасным для одновременного чтения (так как другие потоки не могли пропустить обновления, так как не было обновлений), верно?
 Heath Borders13 мая 2016 г., 17:04
Если он статически инициализирован и никогда не изменяется вне статического блока, то это может быть нормально, потому что вся статическая инициализация синхронизируется с помощьюClassLoader. Это стоит отдельного вопроса самостоятельно. Я бы все равно явно синхронизировал его и профиль, чтобы убедиться, что он вызывает реальные проблемы с производительностью.
 Heath Borders19 сент. 2008 г., 21:25
Многие ответы здесь верны в отношении взаимного исключения из запущенных потоков, но неверны в отношении обновлений памяти. Я соответственно проголосовал за / против, но все еще есть много неправильных ответов с положительными голосами.
 BeeOnRope01 февр. 2017 г., 22:47
@ HeathBorders - что вы подразумеваете под «обновлениями памяти»? JVM - это формальная модель, которая определяет такие вещи, как видимость, атомарность, Происходит, прежде, чем отношения, но не использует такие термины, как «обновления памяти». Вы должны уточнить, предпочтительно используя терминологию из JLS.
 BeeOnRope01 февр. 2017 г., 22:55
@ Дэйв - я полагаю, что вы не ищете ответ через 8 лет, но, к сведению, ключевая путаница почти во всех ответах заключается в том, что они сосредоточены на действия, которые вы совершаете с объектом карты. Вы уже объяснили, что никогда не изменяете объект, так что все это не имеет значения. Единственный потенциальный "гоча" тогда это как вы публикуете ссылку наMap, который ты не объяснил. Если вы не делаете это безопасно, это не безопасно. Если вы делаете это безопасно,эт. Подробности в моем ответе.

Ответы на вопрос(11)

http: //www.docjar.com/html/api/java/util/HashMap.java.htm

Вот источник для HashMap. Как вы можете заметить, там нет абсолютно никакого кода блокировки / мьютекса.

Это означает, что хотя чтение из HashMap в многопоточной среде вполне нормально, я бы определенно использовал ConcurrentHashMap, если было несколько записей.

Что интересно, в .NET HashTable и Dictionary <K, V> есть встроенный код синхронизации.

 Dave L.19 сент. 2008 г., 20:42
Я думаю, что есть классы, в которых простое одновременное чтение может привести к проблемам, например, из-за внутреннего использования временных переменных экземпляра. Так что, вероятно, нужно тщательно изучить исходный код, а не быстрое сканирование кода блокировки / мьютекса.

http: //www.ibm.com/developerworks/java/library/j-jtp03304 # Безопасность инициализации, вы можете сделать ваш HashMap окончательным полем, и после завершения конструктора он будет безопасно опубликован.

... В новой модели памяти существует нечто похожее на отношение «происходит до» между записью конечного поля в конструкторе и начальной загрузкой общей ссылки на этот объект в другом потоке. ...

 Snicolas12 мая 2016 г., 08:44
Этот ответ низкого качества, такой же, как и ответ @taylor gauthier, но с меньшим количеством деталей.
 Ajax30 июн. 2016 г., 03:53
Уммм ... не для того, чтобы быть задницей, но у тебя это задом наперед. Тейлор сказал: «Нет, иди посмотри на это сообщение в блоге, ответ может удивить тебя», тогда как этот ответ на самом деле добавляет что-то новое, чего я не знал ... Об отношениях до и после записи последнего поля в конструктор. Этот ответ превосходен, и я рад, что прочитал его.
 BeeOnRope01 февр. 2017 г., 21:29
Huh? Этотольк правильный ответ я нашел после просмотра ответов с более высоким рейтингом. Ключ безопасно опубликовано и это единственный ответ, который даже упоминает об этом.

при некоторых обстоятельствах get () из несинхронизированного HashMap может вызвать бесконечный цикл. Это может произойти, если одновременный метод put () вызывает перефразирование карты.

http: //lightbody.net/blog/2005/07/hashmapget_can_cause_an_infini.htm

 shmosel30 дек. 2015 г., 20:39
@ AlexMiller, даже если не считать других причин (я полагаю, вы имеете в виду безопасную публикацию), я не думаю, что изменение реализации должно быть причиной ослабления ограничений доступа, если это явно не разрешено документацией. Как это происходит,HashMap Javadoc для Java 8 все еще содержит это предупреждение:Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally.
 Peter Lawrey04 мар. 2009 г., 08:09
На самом деле я видел, как это зависало JVM без использования процессора (что, возможно, хуже)
 Alex Miller02 дек. 2011 г., 21:53
I считат этот код был переписан так, что больше невозможно получить бесконечный цикл. Но вам по-прежнему не следует пытаться получить и положить из несинхронизированного HashMap по другим причинам.

но не с точки зрения памяти. Это то, что широко неправильно понимают разработчики Java, в том числе и здесь, на Stackoverflow. (Соблюдайте рейтинг этот ответ для доказательства.)

Если у вас запущены другие потоки, они могут не увидеть обновленную копию HashMap, если в текущий поток не записана память. Операции записи в память осуществляются с помощью синхронизированных или изменчивых ключевых слов или с использованием некоторых конструкций Java-параллелизма.

Видеть Статья Брайана Гетца о новой модели памяти Java для подробностей.

 Alexander19 сент. 2008 г., 21:18
Извините за двойное подчинение, Хит, я заметил твой только после того, как отправил свой. :)
 Dave L.19 сент. 2008 г., 22:29
На самом деле, хотя ни один поток не увидит объект, прежде чем он будет правильно инициализирован, поэтому я не думаю, что это проблема в этом случае.
 Binita Bharati24 окт. 2015 г., 20:36
Вопрос говорит о том, что после инициализации HashMap он не намерен обновлять ее дальше. С этого момента он просто хочет использовать его в качестве структуры данных только для чтения. Я думаю, это было бы безопасно, при условии, что данные, хранящиеся на его карте, неизменны.
 Bill Michell04 мар. 2009 г., 23:10
Это полностью зависит от того, как инициализируется объект.
 Heath Borders19 сент. 2008 г., 21:21
Я просто рад, что здесь есть другие люди, которые действительно понимают эффекты памяти.

хотя. Доступ к карте безопасен, но в целом не гарантируется, что все потоки будут видеть одно и то же состояние (и, следовательно, значения) HashMap. Это может произойти в многопроцессорных системах, где изменения в HashMap, сделанные одним потоком (например, тем, который его заполнил), могут находиться в кэше этого ЦП и не будут видны потокам, работающим на других ЦП, пока операция ограничения памяти не будет выполнено обеспечение согласованности кэша. Спецификация языка Java в этом однозначна: решение состоит в том, чтобы получить блокировку (синхронизированную (...)), которая испускает операцию ограничения памяти. Итак, если вы уверены, что после заполнения HashMap каждый из потоков получает ЛЮБУЮ блокировку, то с этого момента можно получить доступ к HashMap из любого потока, пока HashMap снова не будет изменен.

 Dave L.19 сент. 2008 г., 22:31
Я не уверен, что поток, получающий доступ к нему, получит какую-либо блокировку, но я уверен, что они не получат ссылку на объект до тех пор, пока он не будет инициализирован, поэтому я не думаю, что они могут иметь устаревшую копию.
 Chris Vest21 сент. 2008 г., 14:46
@ Alex: Ссылка на HashMap может быть изменчивой, чтобы создавать такие же гарантии видимости памяти. @Dave: Этоявляетс можно увидеть ссылки на новые объекты прежде, чем работа его ctor станет видимой для вашей темы.
 damluar03 мая 2016 г., 16:09
Я согласен с Пьером, я не думаю, что достаточно приобрести какой-либо замок. Для того, чтобы изменения стали видимыми, необходимо выполнить синхронизацию на одной и той же блокировке.
 Dave L.24 сент. 2008 г., 01:38
@ Christian В общем случае, конечно. Я говорил, что в этом коде это не так.
 Pierre15 февр. 2016 г., 10:28
Получение СЛУЧАЙНОЙ блокировки не гарантирует очистку всего кэша потока. Это зависит от реализации JVM, и, скорее всего, не так.

java doc (выделение мое):

Обратите внимание, что эта реализация не синхронизирована. Если несколько потоков обращаются к хэш-карте одновременно, и хотя бы один из потоков структурно изменяет карту, она должна быть синхронизирована извне. (Структурное изменение - это любая операция, которая добавляет или удаляет одно или несколько сопоставлений; простое изменение значения, связанного с ключом, который уже содержится в экземпляре, не является структурным изменением.)

Кажется, это подразумевает, что это будет безопасно, если обратное утверждение верно.

 Alex Miller19 сент. 2008 г., 23:06
Пока это отличный совет, как утверждают другие ответы, в случае неизменного, безопасно опубликованного экземпляра карты есть более нюансированный ответ. Но делать это следует только в том случае, если вы знаете, что делаете.
 Dave L.19 сент. 2008 г., 23:13
Надеюсь, что с такими вопросами, многие из нас могут знать, что мы делаем.

бог, когда дело доходит до модели памяти Java, имеет блог из трех частей на эту тему - потому что по сути вы задаете вопрос «Безопасен ли доступ к неизменяемому HashMap» - ответ на этот вопрос - да. Но вы должны ответить на предикат на этот вопрос - «Является ли мой HashMap неизменным». Ответ может вас удивить - в Java достаточно сложный набор правил для определения неизменности.

Для получения дополнительной информации по теме, прочитайте сообщения в блоге Джереми:

Часть 1 о неизменности в Java:http: //jeremymanson.blogspot.com/2008/04/immutability-in-java.htm

Часть 2 о неизменности в Java:http: //jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.htm

Часть 3 о неизменности в Java:http: //jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.htm

 Dave L.19 сент. 2008 г., 21:01
Это хороший момент, но я полагаюсь на статическую инициализацию, при которой ссылки не уходят, поэтому она должна быть безопасной.
 BeeOnRope01 февр. 2017 г., 21:38
Я не вижу, как это высоко оцененный ответ (или даже ответ). С одной стороны, даже неотве вопрос, и в нем не упоминается один ключевой принцип, который будет определять, является ли он безопасным или нет: безопасная публикация. «Ответ» сводится к «это сложно», и вот три (сложные) ссылки, которые вы можете прочитать.

что даже в однопоточном коде замена ConcurrentHashMap на HashMap может быть небезопасной. ConcurrentHashMap запрещает нуль как ключ или значение. HashMap не запрещает их (не спрашивайте).

Так что в маловероятной ситуации, когда ваш существующий код может добавить ноль в коллекцию во время установки (предположительно в случае сбоя какого-либо рода), замена коллекции, как описано, изменит функциональное поведение.

Тем не менее, при условии, что вы больше ничего не делаете, параллельные чтения из HashMap безопасны.

[Редактировать: под «одновременным чтением» я подразумеваю, что нет и одновременных изменений.

Другие ответы объясняют, как это обеспечить. Один из способов - сделать карту неизменной, но это не обязательно. Например, модель памяти JSR133 явно определяет запуск потока как синхронизированного действия, а это означает, что изменения, сделанные в потоке A до его запуска, видны в потоке B.

Мое намерение не противоречить более подробным ответам о модели памяти Java. Этот ответ предназначен для того, чтобы указать, что, несмотря на проблемы с параллелизмом, между ConcurrentHashMap и HashMap есть, по крайней мере, одно различие в API, которое может убрать даже однопоточную программу, которая заменит одну на другую.]

 Steve Jessop04 мар. 2009 г., 00:59
Не в соответствии со статьями, на которые вы сами ссылались: требование состоит в том, чтобы карта не изменялась (и предыдущие изменения должны быть видны всем потокам читателей), а не должна быть неизменной (это технический термин в Java и достаточное, но необязательное условие безопасности).
 Dave L.19 сент. 2008 г., 20:39
Спасибо за предупреждение, но нет попыток использовать нулевые ключи или значения.
 Steve Jessop19 сент. 2008 г., 20:41
Думаю, что не будет. нули в коллекциях - безумный уголок Java.
 Taylor Gautier28 февр. 2009 г., 22:15
Я не согласен с этим ответом. «Одновременное чтение из HashMap безопасно» само по себе неверно. Не указывается, происходит ли чтение с карты, которая является изменчивой или неизменной. Чтобы быть корректным, следует читать «Параллельные чтения из неизменного HashMap безопасны»
 Ajax30 июн. 2016 г., 04:02
Также обратите внимание ... инициализация класса неявно синхронизируется с одной и той же блокировкой (да, вы можете завести тупик в инициализаторах статических полей), поэтому, если ваша инициализация происходит статически, никто не сможет увидеть ее до завершения инициализации, так как они должны быть заблокированы в методе ClassLoader.loadClass для одной и той же полученной блокировки ... И если вам интересно, что разные загрузчики классов имеют разные копии одного и того же поля, вы были бы правы ... но это было бы ортогонально понятие условий гонки; статические поля загрузчика классов разделяют забор памяти.

который вы описали, заключается в том, что вам нужно поместить кучу данных в карту, а затем, когда вы закончите заполнять ее, вы будете считать ее неизменной. Один из подходов, который является «безопасным» (имеется в виду, что вы действительно используете его как неизменяемый), заключается в замене ссылки наCollections.unmodifiableMap(originalMap) когда ты будешь готов сделать его неизменным.

Для примера того, как карты могут плохо работать при одновременном использовании, и предлагаемый обходной путь, о котором я упоминал, ознакомьтесь с этой записью парада ошибок: Bug_id = 6423457

 Dave L.12 нояб. 2009 г., 00:15
Это «безопасно» в том смысле, что оно обеспечивает неизменность, но не решает проблему безопасности потоков. Если карта безопасна для доступа с помощью оболочки UnmodifiableMap, то она безопасна и без нее, и наоборот.

Следующий код сохранен, потому что загрузчик классов позаботится о синхронизации:

public static final HashMap<String, String> map = new HashMap<>();
static {
  map.put("A","A");

}

Следующий код сохранен, потому что запись volatile позаботится о синхронизации.

class Foo {
  volatile HashMap<String, String> map;
  public void init() {
    final HashMap<String, String> tmp = new HashMap<>();
    tmp.put("A","A");
    // writing to volatile has to be after the modification of the map
    this.map = tmp;
  }
}

Это также будет работать, если переменная-член является конечной, поскольку конечная переменная также является переменной. И если метод является конструктором.

Решение Вопроса

если и только есл ссылка на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, чтобы получить доступ к карте, и проверить на ноль, как это:

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 to:

public static volatile HashMap<Object, Object> MAP;

тогда все кошерно: читатели, которые видят Ненулевымначение @ обязательно имеет Происходит, прежде, чем отношения с магазиномMAP и, следовательно, увидеть все магазины, связанные с инициализацией карты.

Другие методы изменяют семантику вашего метода, так как оба (2) (используя статический инициализатор) и (4) (используя Финале) подразумевается, что вы не можете установитьMAP динамически во время выполнения. Если вы ненужн чтобы сделать это, тогда просто объявитеMAP какstatic final HashMap<> и вам гарантирована безопасная публикация.

На практике правила просты для безопасного доступа к «неизмененным объектам»:

Если вы публикуете объект, который не является изначально неизменный (как и во всех объявленных поляхfinal) а также

Вы уже можете создать объект, который будет назначен в момент объявленияa: просто используйтеfinal поле (включаяstatic final для статических членов). Вы хотите назначить объект позже, после того как ссылка уже видна: используйте изменяемое полеb.

Это оно

На практике это очень эффективно. Использованиеstatic finalапример, поле @ позволяет JVM предполагать, что значение не изменяется в течение всего жизненного цикла программы, и сильно его оптимизировать. Использованиеfinal член поля позволяетсамы архитектура для чтения поля способом, эквивалентным нормальному чтению поля, и не препятствует дальнейшей оптимизацииc.

Наконец, использованиеvolatile оказывает определенное влияние: во многих архитектурах не требуется аппаратный барьер (например, x86, особенно те, которые не позволяют чтению передавать чтение), но некоторая оптимизация и переупорядочение могут не произойти во время компиляции - но этот эффект обычно невелик , Взамен вы фактически получаете больше, чем просили - вы можете не только безопасно опубликовать одинHashMap, вы можете хранить как можно больше немодифицированныхHashMap s, если вы хотите использовать ту же ссылку и быть уверенным, что все читатели увидят благополучно опубликованную карту.

Для более подробной информации обратитесь к Shipilev или этот FAQ от Мэнсона и Гетца.

[1] Прямое цитирование из Shipilev.

a Это звучит сложно, но я имею в виду, что вы можете назначить ссылку во время создания - либо в точке объявления, либо в конструкторе (поля-члены) или статическом инициализаторе (статические поля).

b При желании вы можете использоватьsynchronized метод получения / установки илиAtomicReference или что-то еще, но мы говорим о минимальной работе, которую вы можете сделать.

c Некоторые архитектуры с очень слабыми моделями памяти (я смотрю нат, Alpha) может потребоваться какой-то тип барьера для чтения передfinal читай - но сегодня они очень редки.

 BeeOnRope03 нояб. 2018 г., 02:32
Таким образом, дляHashMap методы, которые мы ожидаем быть только для чтения, только для чтения, так как они не изменяют структурноHashMap. Конечно, эта гарантия может не распространяться на любые другиеMap реализации, но вопрос оHashMap конкретно.
 Jiang YD24 окт. 2018 г., 15:27
never modify HashMap не значитstate of the map object думаю, что @ потокобезопасен. Бог знает реализацию библиотеки, если в официальном документе не указано, что она безопасна для потоков.
 BeeOnRope03 нояб. 2018 г., 02:27
@ JiangYD - вы правы, в некоторых случаях там есть серая область: когда мы говорим «изменить», мы на самом деле имеем в виду любое действие, которое внутренне выполняет какое-то Пишет, который может состязаться с чтением или записью в других потоках. Эти записи могут быть внутренними деталями реализации, поэтому даже операция, которая кажется «только для чтения», какget() может фактически выполнить некоторые записи, скажем, обновить некоторую статистику (или в случае упорядоченного доступаLinkedHashMap обновление порядка доступа). Так что хорошо написанный класс должен предоставить некоторую документацию, которая прояснит, если ...
 BeeOnRope03 нояб. 2018 г., 02:29
... очевидно, что операции «только для чтения» действительно внутренне доступны только для чтения в смысле безопасности потоков. Например, в стандартной библиотеке C ++ есть общее правило, что функция-член помечается какconst этом смысле @ действительно доступны только для чтения (внутренне они все еще могут выполнять запись, но они должны быть защищены от потоков). Здесь нетconst ключевое слово в Java, и я не знаю ни о какой документированной полной гарантии, нов цело стандартные библиотечные классы ведут себя как положено, а исключения задокументированы (см.LinkedHashMap пример, где RO работает какget явно упоминаются как небезопасные).
 BeeOnRope03 нояб. 2018 г., 02:30
@ JiangYD - наконец, возвращаясь к исходному вопросу, дляHashMap у нас на самом деле есть прямо в документации поведение безопасности потока для этого класса: Если несколько потоков обращаются к хэш-карте одновременно, и хотя бы один из потоков структурно изменяет карту, она должна быть синхронизирована извне. (Структурная модификация - это любая операция, которая добавляет или удаляет одно или несколько сопоставлений; простое изменение значения, связанного с ключом, который уже содержится в экземпляре, не является структурной модификацией.)

Ваш ответ на вопрос