Чи є HashMap потокобезпечним для різних ключів?


87

Якщо у мене є два декілька потоків, які отримують доступ до HashMap, але я гарантую, що вони ніколи не матимуть одночасного доступу до одного і того ж ключа, чи може це все одно призвести до стану перегони?

Відповіді:


99

У відповіді @ dotsid він говорить так:

Якщо ви будь-яким чином змінили HashMap, тоді ваш код просто зламається.

Він правильний. Карта HashMap, яка оновлюється без синхронізації, зламається, навіть якщо потоки використовують несумісні набори ключів. Ось деякі речі, які можуть піти не так.

  • Якщо один потік робить a put, тоді інший потік може бачити застаріле значення розміру хеш-карти.

  • Коли потік робить putте, що ініціює перебудову таблиці, інший потік може бачити перехідні або застарілі версії посилання на хеш-масив, його розмір, його вміст або ланцюжки хешування. Може настати хаос.

  • Коли потік робить a putдля ключа, який стикається з яким-небудь ключем, який використовується яким-небудь іншим потоком, а останній потік робить a putдля свого ключа, тоді останній може побачити застарілу копію посилання хеш-ланцюжка. Може настати хаос.

  • Коли один потік зондує таблицю ключем, який стикається з одним із ключів іншого потоку, він може зіткнутися з цим ключем на ланцюжку. Він викличе дорівнює за цим ключем, і якщо потоки не синхронізовані, метод рівності може виявити застарілий стан у цьому ключі.

І якщо у вас є дві нитки, які одночасно виконують putабо removeзапитують, існує безліч можливостей для умов перегонів.

Я можу придумати три рішення:

  1. Використовуйте a ConcurrentHashMap.
  2. Використовуйте звичайну, HashMapале синхронізуйте зовні; наприклад, використання примітивних мьютексів, Lockоб’єктів тощо.
  3. HashMapДля кожної нитки використовуйте різні . Якщо потоки дійсно мають несумісний набір ключів, тоді у них не повинно бути потреби (з алгоритмічної точки зору) для спільного використання однієї карти. Дійсно, якщо ваші алгоритми включають потоки, що повторюють ключі, значення або записи карти в певний момент, розбиття однієї карти на кілька карт може дати значний пришвидшення для цієї частини обробки.

30

Просто використовуйте ConcurrentHashMap. ConcurrentHashMap використовує кілька блокувань, які охоплюють цілий ряд хеш-сегментів, щоб зменшити ймовірність оскарження блокування. Існує незначний вплив на ефективність придбання безсумнівного блокування.

Щоб відповісти на ваше вихідне запитання: За словами javadoc, поки структура карти не змінюється, у вас все добре Це означає відсутність видалення елементів і відсутність додавання нових ключів, яких ще немає на карті. Заміна значення, пов’язаного з існуючими ключами, прекрасна.

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

Хоча це не дає жодних гарантій щодо видимості. Тож ви повинні бути готові час від часу приймати отримання застарілих асоціацій.


6

Це залежить від того, що ви маєте на увазі під словом «доступ». Якщо ви просто читаєте, ви можете читати навіть ті самі клавіші, якщо видимість даних гарантована згідно з правилами " до-раніше ". Це означає, що HashMapне слід змінюватись, і всі зміни (початкові конструкції) повинні бути завершені до того, як будь-який зчитувач почне отримувати доступ HashMap.

Якщо ви HashMapбудь-яким чином змінили a, тоді ваш код просто зламався. @Stephen C дає дуже гарне пояснення, чому.

РЕДАГУВАТИ: Якщо перший випадок - це ваша реальна ситуація, я рекомендую вам використовувати, Collections.unmodifiableMap()щоб бути впевненим, що ваша HashMap ніколи не змінюється. Об'єкти, на які вказує, HashMapтакож не повинні змінюватися, тому агресивне використання finalключового слова може вам допомогти.

І як каже @Lars Andren, ConcurrentHashMapце найкращий вибір у більшості випадків.


2
На мою думку, ConcurrentHashMap - найкращий вибір. Єдина причина, чому я його не рекомендував, тому що автор не запитував :) Він має меншу пропускну здатність через операції CAS, але, як говорить золоте правило паралельного програмування: "Зробіть це правильно, і лише тоді робіть це швидко ":)
Денис Баженов

unmodifiableMapгарантує, що клієнт не може змінити карту. Це нічого не гарантує, що основна карта не буде змінена.
Піт Кіркхем,

Як я вже зазначав: "Об'єкти, на які вказує HashMap, також не повинні змінюватися"
Денис Баженов

4

Зміна HashMap без належної синхронізації з двох потоків може легко призвести до перегонового стану.

  • Коли a put()веде до зміни розміру внутрішньої таблиці, це займає деякий час, а інший потік продовжує запис у стару таблицю.
  • Два put()для різних ключів призводять до оновлення того самого сегмента, якщо хеш-коди ключів рівні за модулем розміру таблиці. (Насправді взаємозв'язок між хеш-кодом та індексом сегмента є більш складним, але зіткнення все ще можуть відбуватися.)

1
Це гірше, ніж просто умови перегонів. Залежно від внутрішніх елементів HashMapреалізації, яку ви використовуєте, ви можете отримати пошкодження HashMapструктур даних тощо, спричинені аномаліями пам'яті.
Стівен С
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.