Вопрос с собеседования · Java

Несколько потоков одновременно пишут в обычную HashMap. Чем это грозит?

JuniorКоллекции
Ещё спрашивают так:
  • Потокобезопасен ли HashMap?
  • Чем ConcurrentHashMap отличается от Collections.synchronizedMap?
Коротко

HashMap не потокобезопасен: параллельная запись ломает структуру — потерянные записи, неверный size, в старых версиях — зависание обхода.

  • Ошибка проявляется не сразу и далеко от места проблемы
  • Решение — ConcurrentHashMap; synchronizedMap проще, но одна блокировка на всё

А ещё Даже потокобезопасная карта не спасает составные операции «проверь, затем положи» — нужны compute/merge/putIfAbsent

Подробно

HashMap не рассчитан на одновременную запись из нескольких потоков. Два потока могут одновременно вставлять в одну корзину или запустить расширение таблицы — и одна из записей потеряется, size() станет неверным. В Java 7 и раньше параллельное расширение могло зациклить список в корзине, и обход зависал навсегда.

Коварство в том, что исключения обычно нет: данные просто оказываются неверными, и это обнаруживается далеко от места проблемы.

Решения:

  • **ConcurrentHashMap** — спроектирован для параллельного доступа: чтение без блокировок, запись блокирует только конкретную корзину;
  • **Collections.synchronizedMap** — обёртка с одной блокировкой на всю карту: проще, но потоки ждут друг друга; итерировать нужно, вручную синхронизируясь на карте.

Отдельная ловушка — составные операции. Код «если ключа нет — положить» из двух вызовов не атомарен даже на потокобезопасной карте: между проверкой и вставкой вмешается другой поток. Нужны атомарные методы: putIfAbsent, computeIfAbsent, merge.

Пример

Map<String, Integer> counts = new ConcurrentHashMap<>();
// counts.put(word, counts.getOrDefault(word, 0) + 1); // гонка
counts.merge(word, 1, Integer::sum);                  // атомарно

Что хотят услышать

  • HashMap не потокобезопасен, последствия параллельной записи.
  • ConcurrentHashMap против synchronizedMap.
  • Неатомарность составных операций и атомарные методы.

Частые ошибки

  • Считать отсутствие исключения признаком корректной работы.
  • Полагаться на синхронизирующую обёртку при составных операциях.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✗Будет брошено ConcurrentModificationException при первой же параллельной записи
  • ✗Ничего: карта синхронизирована по умолчанию
  • ✗Записи просто выполнятся медленнее
  • ✓Порчей структуры: потерянные записи, неверный size

Что спросят дальше

  • Чем конкурентная реализация лучше синхронизирующей обёртки?
  • Почему в конкурентной карте запрещены null?

Разбор темы

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования