В чем разница между HashMap и ConcurrentHashMap
Что проверяют
Понимаете ли вы, что потокобезопасность отдельных операций не равна безопасности составных, и знаете ли, почему null запрещён.
Как отвечать
HashMap не потокобезопасен. При конкурентной записи он может потерять данные, а в версиях до Java 8 конкурентное перестроение таблицы приводило к зацикливанию списка в корзине, и поток навсегда зависал на get.
ConcurrentHashMap безопасен и при этом не блокирует таблицу целиком. С Java 8 схема такая: вставка в пустую корзину идёт атомарной операцией сравнения с обменом, а при коллизии блокируется только первый узел этой корзины. Чтение вообще не берёт блокировок, потому что узлы объявлены изменчивыми. Отсюда высокая пропускная способность при множестве потоков.
Отличия, которые надо назвать:
nullзапрещён и для ключа, и для значения. Причина не техническая, а смысловая: в конкурентной карте нельзя различить «ключа нет» и «значение равноnull», потому что междуgetиcontainsKeyсостояние успевает измениться. ВHashMapодинnull-ключ и любыеnull-значения допустимы;- итератор слабо согласованный: он не бросает
ConcurrentModificationExceptionи отражает состояние на момент создания или позже. ИтераторHashMapпри изменении во время обхода падает; size()приблизителен и рассчитан на оценку, а не на точную арифметику.
Ключевое практическое замечание: потокобезопасность отдельных операций не делает безопасной составную. Последовательность «проверил, что ключа нет, и положил» это гонка даже в ConcurrentHashMap. Для этого есть атомарные методы: putIfAbsent, computeIfAbsent, compute, merge. Именно computeIfAbsent берут для ленивой инициализации кэша.
Старый вариант Collections.synchronizedMap тоже безопасен, но блокирует всю карту на каждую операцию и потому заметно медленнее.
Где спотыкаются
Пишут «проверил и положил» двумя вызовами.
Не знают, почему запрещён null.
Считают Collections.synchronizedMap равноценной заменой.
Вызывают долгую функцию внутри computeIfAbsent, блокируя корзину.