Является ли HashMap в Java потокобезопасной?

Ответ

Нет, HashMap не является потокобезопасной. При одновременной модификации из нескольких потоков без внешней синхронизации возможны:

  • Потеря данных при добавлении.
  • Повреждение внутренней структуры (например, образование циклических ссылок в бакетах), что может привести к бесконечному циклу при итерации.
  • ConcurrentModificationException при одновременной итерации и модификации.

Потокобезопасные альтернативы:

  1. ConcurrentHashMap (предпочтительный выбор):

    Map<String, String> concurrentMap = new ConcurrentHashMap<>();
    • Использует сегментированную блокировку или операции без блокировок (в новых версиях), что обеспечивает высокую производительность при параллельном чтении и записи.
    • Гарантирует атомарность отдельных операций (например, putIfAbsent).
  2. Collections.synchronizedMap() (обертка с глобальной блокировкой):

    Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
    • Синхронизирует все методы с помощью мьютекса на самом объекте-обертке.
    • Менее производительна при высокой конкуренции, так как блокирует всю коллекцию на время каждой операции.
    • Важно: Для безопасного перебора (iteration) всё равно необходимо использовать ручную синхронизацию:
      synchronized(syncMap) {
      for (Map.Entry<String, String> entry : syncMap.entrySet()) {
          // ...
      }
      }

Ответ 18+ 🔞

А, слушай, вот это реально важный момент, который многие на собеседовании пролетают, как фанера над Парижем. Сейчас разжуём.

Так вот, этот ваш HashMap — он, блядь, как та самая мартышлюшка с гранатой в толпе. В одном потоке — красота, всё работает. Но как только на него с разных сторон начинают наваливаться потоки с модификациями... Ёперный театр, пиздец полный.

Что конкретно может случиться, если его без защиты в многопоточку пихать? Да хуйня же!

  • Данные нахуй пропадут. Добавил элемент — а его как не бывало. Волшебство, блядь.
  • Внутренности сломаются. Он там ссылки между ячейками хранит, так эти ссылки могут в ебучую циклическую петлю превратиться. И потом, когда ты попробуешь по нему пройтись, твой поток уйдёт в бесконечный цикл — ищи-свищи, что за ебушки-воробушки.
  • Вылетит ConcurrentModificationException. Классика жанра. Один поток итерирует, другой в это же время что-то пихает внутрь — и тебе в лог прилетает эта радость.

Короче, доверия к нему в таком режиме — ноль ебать.

Так что же делать, ёпта? Есть же варианты!

Вариант первый, правильный и современный — ConcurrentHashMap.

Map<String, String> concurrentMap = new ConcurrentHashMap<>();

Вот это, сука, работяга. Он не тупо весь на замок вешает. У него там или сегменты свои, или вообще lock-free алгоритмы в новых версиях. Чтение почти никогда не блокируется, запись — умно. Производительность — овердохуища по сравнению с тупой синхронизацией всего. И отдельные операции у него атомарные из коробки — типа putIfAbsent. Красота!

Вариант второй, для любителей старины и глобальных блокировок — Collections.synchronizedMap().

Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());

Это как надеть на HashMap смирительную рубашку. Каждый вызов метода (put, get) теперь обёрнут в synchronized. Но, внимание, ёбта! Это не делает итерацию безопасной! Это главная ловушка!

Представь: один поток начал перебор, другой в это время что-то засунул — и опять тебе ConcurrentModificationException в ебало. Чтобы этого не было, надо ручками синхронизировать:

synchronized(syncMap) {
    for (Map.Entry<String, String> entry : syncMap.entrySet()) {
        // делаем что-то
    }
}

Производительность у такой обёртки, конечно, так себе, особенно если писателей много. Все друг другу мешают, как пьяные в узком коридоре.

Итог: если нужна потокобезопасность — бери ConcurrentHashMap и не мучайся. synchronizedMap — это для специфичных легаси-кейсов или когда тебе прямо вот позарез нужна глобальная блокировка на всё. А голый HashMap в многопоточку — это прямой путь в дебаг-ад, чувак.