← Назад к списку
ТехническаяJava и KotlinMiddle

Чем ConcurrentHashMap отличается от Collections.synchronizedMap и HashMap? Как он устроен внутри?

Короткий ответ

  • HashMap не потокобезопасен: гонки при resize ломают структуру
  • synchronizedMap — один монитор на всю карту, низкий параллелизм и нет атомарных compute
  • ConcurrentHashMap синхронизирует только головы корзин + CAS
  • Чтения вообще без блокировок, записи конкурируют лишь в одной корзине
  • Атомарные методы: computeIfAbsent, merge, putIfAbsent
  • Итераторы weakly consistent, не кидают ConcurrentModificationException
  • null-ключи и null-значения запрещены

ConcurrentHashMap даёт высокий параллелизм за счёт мелкозернистых блокировок и CAS, плюс атомарные операции над записями.

Как сказать вслух

пример ответа

Обычный HashMap в многопоточке просто ломается — вплоть до потери данных при одновременном resize. SynchronizedMap решает это одним замком на всю карту, но потоки выстраиваются в очередь даже на чтение. ConcurrentHashMap блокирует только одну корзину при записи, а читает вообще без блокировок. Плюс у него есть атомарные операции вроде computeIfAbsent — с synchronizedMap проверку и вставку пришлось бы оборачивать внешним замком.

Подробный ответ

Основной ответ

С Java 8 ConcurrentHashMap — это массив корзин, где вставка в пустую корзину делается CAS-ом, а при коллизии синхронизируется только первый узел корзины; resize выполняется кооперативно несколькими потоками. Чтения не блокируются (volatile-семантика ссылок на узлы). Отсюда высокий параллелизм против synchronizedMap, где каждый метод держит общий монитор. Важное отличие — атомарные составные операции: putIfAbsent, computeIfAbsent, merge исключают гонку check-then-act. Итераторы weakly consistent: отражают состояние на момент создания и не бросают ConcurrentModificationException. null запрещён, потому что get == null стал бы неоднозначным. size() — приблизительная агрегация счётчиков, без глобальной остановки.

Ключевые моменты

  • Гранулярность блокировок. CAS для пустых корзин, synchronized на голове корзины при коллизиях — конкуренция только точечная.
  • Атомарные операции. computeIfAbsent/merge решают check-then-act без внешних замков; внутри функции нельзя лезть в ту же карту.
  • Итерация. Weakly consistent — безопасно итерироваться при параллельных изменениях, но снимок не точный.
  • Ограничения. Нет null; атомарность только на уровне одной записи, инварианты между ключами не защищены.

Практический контекст

Это рабочая лошадка кэшей и реестров в сервисах: карта «ключ → соединение/метрика/лок». Интервьюер проверяет, что кандидат не пишет if (!map.containsKey(k)) map.put(...) в конкурентном коде, а использует computeIfAbsent. Продвинутый вопрос вдогонку — долгие вычисления в computeIfAbsent блокируют корзину, поэтому для тяжёлых загрузок кладут в карту CompletableFuture или берут Caffeine.

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

  • Описывают сегменты из Java 7, не зная, что с Java 8 устройство другое
  • Думают, что ConcurrentHashMap делает атомарной любую последовательность операций
  • Кладут блокирующие вычисления в computeIfAbsent, подвешивая другие потоки той же корзины

ИП Кочкин Алексей Сергеевич · ИНН 390509026279 · ОГРНИП 325390000030973 · jiniys2005@yandex.ru