Если у меня есть два нескольких потока, обращающихся к HashMap, но гарантирую, что они никогда не будут обращаться к одному и тому же ключу одновременно, может ли это привести к состоянию гонки?
Если у меня есть два нескольких потока, обращающихся к HashMap, но гарантирую, что они никогда не будут обращаться к одному и тому же ключу одновременно, может ли это привести к состоянию гонки?
Ответы:
В ответе @dotsid он говорит следующее:
Если вы каким-либо образом измените HashMap, ваш код просто сломается.
Он прав. HashMap, обновленный без синхронизации, сломается, даже если потоки используют непересекающиеся наборы ключей. Вот некоторые из вещей, которые могут пойти не так.
Если один поток выполняет a put
, то другой поток может увидеть устаревшее значение размера хэш-карты.
Когда поток выполняет put
операцию, которая запускает перестройку таблицы, другой поток может видеть временные или устаревшие версии ссылки на массив хэш-таблицы, ее размер, ее содержимое или цепочки хеш-кодов. Может возникнуть хаос.
Когда поток выполняет операцию put
для ключа, который сталкивается с некоторым ключом, используемым другим потоком, а последний поток выполняет операцию put
для своего ключа, тогда последний может увидеть устаревшую копию ссылки на цепочку хэшей. Может возникнуть хаос.
Когда один поток исследует таблицу с ключом, который сталкивается с одним из ключей другого потока, он может встретить этот ключ в цепочке. Он вызовет равенство для этого ключа, и если потоки не синхронизированы, метод equals может столкнуться с устаревшим состоянием в этом ключе.
И если у вас есть два потока, одновременно выполняющих put
или remove
запрашивающих, существует множество возможностей для состояний гонки.
Я могу придумать три решения:
ConcurrentHashMap
.HashMap
но синхронизируйте снаружи; например, с использованием примитивных мьютексов, Lock
объектов и т. д.HashMap
для каждого потока. Если потоки действительно имеют непересекающийся набор ключей, тогда не должно быть необходимости (с алгоритмической точки зрения) для них совместно использовать одну карту. В самом деле, если ваши алгоритмы включают потоки, повторяющие ключи, значения или записи карты в какой-то момент, разделение одной карты на несколько карт может дать значительное ускорение для этой части обработки.Просто используйте ConcurrentHashMap. ConcurrentHashMap использует несколько блокировок, которые охватывают диапазон хэш-сегментов, чтобы уменьшить вероятность оспаривания блокировки. Получение неоспариваемой блокировки оказывает незначительное влияние на производительность.
Чтобы ответить на ваш исходный вопрос: согласно javadoc, пока структура карты не меняется, все в порядке. Это означает отсутствие удаления элементов и добавления новых ключей, которых еще нет на карте. Заменить значение, связанное с существующими ключами, можно.
Если несколько потоков обращаются к хеш-карте одновременно, и хотя бы один из потоков структурно модифицирует карту, она должна быть синхронизирована извне. (Структурная модификация - это любая операция, которая добавляет или удаляет одно или несколько сопоставлений; простое изменение значения, связанного с ключом, который уже содержит экземпляр, не является структурной модификацией.)
Хотя это не дает никаких гарантий относительно видимости. Поэтому вы должны быть готовы время от времени получать устаревшие ассоциации.
Это зависит от того, что вы имеете в виду под «доступом». Если вы просто читаете, вы можете читать даже те же ключи, если видимость данных гарантируется в соответствии с правилами « происходит раньше ». Это означает, что это HashMap
не должно меняться, и все изменения (начальные конструкции) должны быть завершены до того, как любой читатель начнет доступ HashMap
.
Если вы HashMap
каким-либо образом измените a, ваш код просто сломается. @Stephen C дает очень хорошее объяснение, почему.
РЕДАКТИРОВАТЬ: Если первый случай - это ваша реальная ситуация, я рекомендую вам использовать, Collections.unmodifiableMap()
чтобы быть уверенным, что ваш HashMap никогда не изменяется. Объекты, на которые указывает, также HashMap
не должны изменяться, поэтому агрессивное использование final
ключевого слова может вам помочь.
И, как говорит @Lars Andren, ConcurrentHashMap
в большинстве случаев это лучший выбор.
unmodifiableMap
гарантирует, что клиент не сможет изменить карту. Он ничего не делает, чтобы гарантировать, что основная карта не изменится.
Изменение HashMap без надлежащей синхронизации из двух потоков может легко привести к состоянию гонки.
put()
приводит к изменению размера внутренней таблицы, это занимает некоторое время, и другой поток продолжает запись в старую таблицу.put()
для разных ключей приводят к обновлению одного и того же блока, если хэш-коды ключей равны по модулю размера таблицы. (На самом деле связь между хэш-кодом и индексом корзины более сложная, но коллизии все же могут возникать.)HashMap
реализации, которое вы используете, вы можете получить повреждение HashMap
структур данных и т.д., вызванное аномалиями памяти.