Получить доступ

Как устроен ConcurrentHashMap в Java 8+ и почему он эффективнее synchronized Map?

SeniorКоллекции
Ещё спрашивают так:
  • Почему ConcurrentHashMap быстрее Collections.synchronizedMap?
Коротко

Java 8+: массив корзин; вставка в пустую корзину — CAS без блокировки, при коллизии — synchronized на первом узле корзины. Блокируется только одна корзина, а чтение вовсе без блокировок (volatile-поля).

  • synchronizedMap и Hashtable — один замок на всю карту: все операции по очереди
  • Длинная цепочка превращается в дерево, как в HashMap
  • Расширение таблицы выполняют несколько потоков совместно

А ещё Атомарные составные операции: computeIfAbsent, merge, putIfAbsent

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Попробуйте сами

HashMap: put, коллизия и resize

Подробно

Проблема synchronized Map: Collections.synchronizedMap и Hashtable берут один монитор на каждый вызов. Восемь потоков, читающих разные ключи, всё равно выстраиваются в очередь.

Устройство ConcurrentHashMap (Java 8+):

  • массив корзин Node[] с volatile-доступом к элементам;
  • чтение (get) — без блокировок: значения и ссылки узлов volatile, видимость обеспечена;
  • вставка в пустую корзину — CAS (compare-and-set) первого узла: без блокировки, повтор при гонке;
  • вставка при коллизии — synchronized на первом узле этой корзины: блокируется только она, остальные корзины доступны параллельно;
  • цепочки длиннее 8 при таблице от 64 превращаются в красно-чёрные деревья;
  • размер — распределённые счётчики (как LongAdder), поэтому size() приблизителен под нагрузкой;
  • расширение — новые потоки, обратившиеся к карте во время resize, помогают переносить корзины.

В Java 7 использовались сегменты — 16 отдельных замков на части таблицы.

Важно для кода: отдельные операции атомарны, но «проверить, потом положить» из двух вызовов — гонка. Используют putIfAbsent, computeIfAbsent, compute, merge. null ключи и значения запрещены: get возвращает null как «нет ключа», и отличить было бы нельзя.

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

  • CAS для пустой корзины и замок на корзину при коллизии.
  • Чтение без блокировок.
  • Сравнение с synchronizedMap и сегментами Java 7.
  • Атомарные составные операции, запрет null.

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

  • «ConcurrentHashMap блокирует сегменты» — так было до Java 8.
  • Делать get-then-put вместо computeIfAbsent.

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

  • Почему null-ключи/значения запрещены?
  • Что такое weakly consistent iterator?

Разбор темы

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

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем