Вопрос:

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

java multithreading concurrency hashmap

57556 просмотра

11 ответа

35099 Репутация автора

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

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

Автор: Dave L. Источник Размещён: 19.09.2008 06:11

Ответы (11)


0 плюса

111763 Репутация автора

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

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

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

Интересно, что как .NET HashTable, так и Dictionary построили код синхронизации.

Автор: FlySwat Размещён: 19.09.2008 06:19

10 плюса

35099 Репутация автора

После немного более пристального взгляда я нашел это в java-документе (внимание мое):

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

Это, по-видимому, подразумевает, что это будет безопасно, предполагая, что обращение к утверждению верно.

Автор: Dave L. Размещён: 19.09.2008 06:20

1 плюс

230813 Репутация автора

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

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

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

[Edit: by «concurrent reads», я имею в виду, что не происходит одновременных изменений.

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

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

Автор: Steve Jessop Размещён: 19.09.2008 06:38

70 плюса

3880 Репутация автора

Джереми Мэнсон, бог, когда дело доходит до модели памяти 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

Автор: Taylor Gautier Размещён: 19.09.2008 06:56

34 плюса

19276 Репутация автора

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

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

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

Автор: Heath Borders Размещён: 19.09.2008 07:10

8 плюса

7000 Репутация автора

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

Автор: Alexander Размещён: 19.09.2008 07:16

9 плюса

52915 Репутация автора

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

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

Автор: Alex Miller Размещён: 19.09.2008 09:13

3 плюса

31 Репутация автора

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

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

Автор: Will Размещён: 09.11.2009 04:37

4 плюса

267 Репутация автора

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

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

Автор: bodrin Размещён: 30.08.2012 04:08

0 плюса

627 Репутация автора

Если инициализация и каждый случай синхронизированы, вы сохраняете.

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

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 также нестабилен. И если метод является конструктором.

Автор: TomWolk Размещён: 28.09.2016 11:38

29 плюса

24406 Репутация автора

Решение

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

  1. Обмен ссылками через правильно заблокированное поле ( JLS 17.4.5 )
  2. Используйте статический инициализатор для инициализации магазинов ( JLS 12.4 )
  3. Обмен ссылками через поле volatile ( JLS 17.4.5 ) или как следствие этого правила через классы AtomicX
  4. Инициализируйте значение в конечном поле ( 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для статических членов).
  • Вы хотите назначить объект позже, после того, как ссылка уже видна: используйте поле volatile b .

Это оно!

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

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

Для получения дополнительной информации обратитесь к Shipilev или к этим часто задаваемым вопросам от Manson и Goetz .


[1] Непосредственно цитирование со стороны судового .


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

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

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

Автор: BeeOnRope Размещён: 01.02.2017 09:45
Вопросы из категории :
32x32