Якщо у мене є два декілька потоків, які отримують доступ до HashMap, але я гарантую, що вони ніколи не матимуть одночасного доступу до одного і того ж ключа, чи може це все одно призвести до стану перегони?
Якщо у мене є два декілька потоків, які отримують доступ до HashMap, але я гарантую, що вони ніколи не матимуть одночасного доступу до одного і того ж ключа, чи може це все одно призвести до стану перегони?
Відповіді:
У відповіді @ dotsid він говорить так:
Якщо ви будь-яким чином змінили HashMap, тоді ваш код просто зламається.
Він правильний. Карта HashMap, яка оновлюється без синхронізації, зламається, навіть якщо потоки використовують несумісні набори ключів. Ось деякі речі, які можуть піти не так.
Якщо один потік робить a put
, тоді інший потік може бачити застаріле значення розміру хеш-карти.
Коли потік робить put
те, що ініціює перебудову таблиці, інший потік може бачити перехідні або застарілі версії посилання на хеш-масив, його розмір, його вміст або ланцюжки хешування. Може настати хаос.
Коли потік робить a put
для ключа, який стикається з яким-небудь ключем, який використовується яким-небудь іншим потоком, а останній потік робить a put
для свого ключа, тоді останній може побачити застарілу копію посилання хеш-ланцюжка. Може настати хаос.
Коли один потік зондує таблицю ключем, який стикається з одним із ключів іншого потоку, він може зіткнутися з цим ключем на ланцюжку. Він викличе дорівнює за цим ключем, і якщо потоки не синхронізовані, метод рівності може виявити застарілий стан у цьому ключі.
І якщо у вас є дві нитки, які одночасно виконують 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
структур даних тощо, спричинені аномаліями пам'яті.