Как работает ConcurrentHashMap, есть ли у него локи и как это влияет на производительность
Что проверяют
Можете ли вы точно сказать, где в ConcurrentHashMap блокировки есть, где их нет, и что из этого следует под нагрузкой. Следом обычно спрашивают про горячий ключ и про долгую лямбду в computeIfAbsent.
Как отвечать
Ответ «локи есть, но мелкие» разложите по операциям (Java 8 и новее):
- чтение:
getи итерация идут без блокировок, через volatile-чтения. Читатели не ждут писателей и хорошо масштабируются по ядрам; - вставка в пустой бакет: CAS без монитора. Проиграв гонку, поток просто повторяет попытку;
- изменение непустого бакета:
synchronizedна первом узле. Блокируется одна цепочка, а не таблица, конфликтуют только потоки, попавшие в один бакет; compute,computeIfAbsent,merge: лямбда выполняется под той же блокировкой бакета, атомарность оплачивается временем удержания лока;- ресайз: не глобальная пауза. Таблица переносится порциями, пишущие потоки помогают, а читатели через
ForwardingNodeнаходят данные в новой таблице; - счётчик размера: разнесён по ячейкам, чтобы вставки не бились за одно поле.
Выводы для производительности. Запись масштабируется, пока ключи распределены равномерно: плохой hashCode сваливает всё в несколько бакетов, и потоки встают в очередь на одном мониторе. Горячий ключ даёт тот же эффект, счётчики разгружают через LongAdder в значении. Частые ресайзы снимаются начальной ёмкостью в конструкторе. Против synchronizedMap выигрыш заметен при многих потоках, а в однопоточном коде HashMap быстрее. Если нужен порядок ключей, берут ConcurrentSkipListMap: он неблокирующий, но доступ логарифмический.
Где спотыкаются
Отвечают «локов нет, всё на CAS». CAS работает только для пустого бакета, остальные записи идут под synchronized.
Рассказывают про сегментные и «каскадные» локи. Сегментов нет с Java 8, а каскадных блокировок в этом классе не было никогда.
Кладут в computeIfAbsent сетевой вызов или тяжёлое вычисление. Остальные записи в этот бакет ждут, а попытка изменить ту же карту изнутри лямбды заканчивается IllegalStateException или зависанием.