Безопасно ли получать значения из java.утиль.HashMap из нескольких потоков (без изменений)?
есть случай, когда карта будет построена, и как только она будет инициализирована, она никогда не будет изменена снова. Однако он будет доступен (только через get (key)) из нескольких потоков. Безопасно ли использовать java.util.HashMap в этом случае?
(В настоящее время я с удовольствием использую java.util.concurrent.ConcurrentHashMap, и не имеют измеренной потребности улучшить производительность, но мне просто любопытно, если простой HashMap хватило бы. Следовательно, этот вопрос не " какой из них я должен использовать?"и это вопрос производительности. Скорее, вопрос в том, будет ли это безопасно?")
11 ответов:
ваша идиома безопасна если и только если ссылка
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) применяется непосредственно к код у меня выше: если вы преобразуете объявление
MAPto:public static volatile HashMap<Object, Object> MAP;тогда все кошерно: читатели, которые видят non-null значение должно быть происходит-перед связь с магазином в
MAPи, следовательно, увидеть все магазины, связанные с инициализацией карты.другие методы изменяют семантику вашего метода, так как оба (2) (используя статический инициализатор) и (4) (используя финал) означает, что вы не можете установить
MAPдинамически во время выполнения. Если ты этого не сделаешь нужно чтобы сделать это, то просто объявитьMAPкакstatic final HashMap<>и вам гарантирована безопасная публикация.на практике правила просты для безопасного доступа к "никогда не измененным объектам":
если вы публикуете объект, который не является по своей сути неизменный (как и во всех полей, объявленных
final) и:
- вы уже можете создать объект, который будет присвоен в момент объявленияa: просто использовать
Джереми Мэнсон, Бог, когда дело доходит до модели памяти Java, имеет три части блога на эту тему - потому что по сути вы задаете вопрос "Безопасно ли получить доступ к неизменяемой HashMap" - ответ на это да. Но вы должны ответить на предикат на этот вопрос, который является - "является ли мой HashMap неизменным". Ответ может вас удивить-Java имеет относительно сложный набор правил для определения неизменности.
для получения дополнительной информации по этой теме, читать блог Джереми сообщения:
Часть 1 О неизменяемости в Java: http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html
Часть 2 О неизменяемости в Java: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html
Часть 3 О неизменяемости в Java: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.html
чтение безопасно с точки зрения синхронизации, но не с точки зрения памяти. Это то, что широко неправильно понимается среди разработчиков Java, в том числе здесь, на Stackoverflow. (Обратите внимание на рейтинг ответ для доказательства.)
Если у вас запущены другие потоки, они могут не увидеть обновленную копию HashMap, если нет записи в память из текущего потока. Записи в память происходят с помощью синхронизированных или изменчивых ключевых слов, или через использование некоторых конструкций параллелизма java.
посмотреть статья Брайана Гетца о новой модели памяти Java для сведения.
после немного больше смотреть, я нашел это в документации (выделено мной):
отметьте, что эта реализация не синхронизированный. если несколько потоков доступ к карте хеша одновременно, и по крайней мере один из потоков изменяет карта структурно, она должна быть синхронизироваться внешне. (структурное модификация-это любая операция, которая добавление или удаление одного или нескольких сопоставлений; просто изменение значения, связанного с ключом что экземпляр уже есть содержит не является структурным модификация.)
Это, кажется, означает, что это будет безопасно, предполагая, что обратное утверждение верно.
одно замечание заключается в том, что при некоторых обстоятельствах get() из несинхронизированной хэш-карты может вызвать бесконечный цикл. Это может произойти, если параллельный put() вызывает повторный хэш карты.
http://lightbody.net/blog/2005/07/hashmapget_can_cause_an_infini.html
есть важный поворот, хотя. Доступ к карте безопасен, но в целом не гарантируется, что все потоки будут видеть точно такое же состояние (и, следовательно, значения) хэш-карты. Это может произойти в многопроцессорных системах, где изменения хэш-карты, выполненные одним потоком (например, тот, который его заполнил), могут находиться в кэше этого процессора и не будут видны потоками, работающими на других процессорах, пока не будет выполнена операция ограждения памяти, обеспечивающая когерентность кэша. язык Java Спецификация Явна на этом: решение состоит в том, чтобы получить блокировку (synchronized (...)) который выдает операцию ограждения памяти. Итак, если вы уверены, что после заполнения хэш-карты каждый из потоков получает любую блокировку, то с этого момента можно получить доступ к хэш-карте из любого потока, пока хэш-карта не будет изменена снова.
согласно http://www.ibm.com/developerworks/java/library/j-jtp03304/ # безопасность инициализации вы можете сделать свою HashMap конечным полем, и после завершения конструктора он будет безопасно опубликован.
... В новой модели памяти есть что-то похожее на связь "происходит до" между записью конечного поля в конструкторе и начальной загрузкой общей ссылки на этот объект в другом потоке. ...
Итак, сценарий, который вы описываете, заключается в том, что вам нужно поместить кучу данных в карту, а затем, когда вы закончите ее заполнять, вы рассматриваете ее как неизменную. Один из подходов, который является "безопасным" (что означает, что вы применяете, что он действительно рассматривается как неизменяемый), заключается в замене ссылки на коллекции.unmodifiableMap (originalMap), когда вы будете готовы сделать его неизменяемым.
для примера того, как плохо карты могут потерпеть неудачу, если они используются одновременно, и предложенный обходной путь, о котором я упоминал, проверьте из этой записи парада ошибок:bug_id=6423457
имейте в виду, что даже в однопоточном коде замена ConcurrentHashMap на HashMap может быть небезопасной. ConcurrentHashMap запрещает null в качестве ключа или значения. HashMap не запрещает их (не спрашивайте).
поэтому в маловероятной ситуации, когда ваш существующий код может добавить null в коллекцию во время установки (предположительно в случае сбоя какого-либо рода), замена коллекции, как описано, изменит функциональное поведение.
Что сказал, при условии, что вы делаете ничто другое одновременное чтение из хэш-карты не безопасно.
[Edit: под" параллельными чтениями " я имею в виду, что нет также параллельных модификаций.
другие ответы объясняют, как это обеспечить. Один из способов - сделать карту неизменной, но это не обязательно. Например, модель памяти JSR133 явно определяет запуск потока как синхронизированное действие, что означает, что изменения, внесенные в поток A перед запуском потока B, видны в потоке B.
мой намерение состоит в том, чтобы не противоречить этим более подробным ответам о модели памяти Java. Этот ответ предназначен для того, чтобы указать, что даже помимо проблем параллелизма существует по крайней мере одно различие API между ConcurrentHashMap и HashMap, которое может скупать даже однопоточную программу, которая заменила одну на другую.]
http://www.docjar.com/html/api/java/util/HashMap.java.html
вот источник для HashMap. Как вы можете сказать, там нет абсолютно никакого кода блокировки / мьютекса.
Это означает, что, хотя его можно читать из HashMap в многопоточной ситуации, я бы определенно использовал ConcurrentHashMap, если бы было несколько записей.
Что интересно, что и хэш-таблица .NET, и словарь
имеют встроенную синхронизацию код.
Если инициализация и каждый put синхронизированы, вы сохраняете.
следующий код сохраняется, потому что загрузчик классов позаботится о синхронизации:
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; } }Это также будет работать, если переменная-член является окончательной, потому что final также изменчива. И если метод является конструктором.
Comments