Зачем отдельное семейство
Обычные коллекции не потокобезопасны. Обёртки Collections.synchronizedMap/List берут единый замок на всю коллекцию — это корректно, но превращает любой доступ в узкое место и всё равно требует ручной синхронизации при итерации.
Конкурентные коллекции из java.util.concurrent спроектированы под параллелизм: тонкая блокировка или вовсе lock-free чтение, плюс weakly-consistent итераторы без CME.
ConcurrentHashMap: устройство
Современный ConcurrentHashMap (с Java 8) отказался от старых сегментов. Теперь это та же таблица бакетов, что у HashMap, но с потокобезопасным доступом по бакетам: вставка в пустой бакет делается атомарно через CAS, а при коллизии берётся synchronized на головном узле конкретного бакета.
Так конкурентные операции над разными бакетами не мешают друг другу, и масштабируемость на запись близка к числу бакетов. Treeify и resize работают, причём ресайз кооперативный — потоки помогают переносить бакеты.
Почему запрещены null
ConcurrentHashMap не допускает null ни как ключ, ни как значение. Причина — неоднозначность в конкурентной среде: map.get(k) == null нельзя трактовать ни как «ключа нет», ни как «значение null», и без блокировки это не различить (containsKey мог бы устареть к следующей строке).
Обычный HashMap такую неоднозначность разрешает в однопоточном коде, поэтому там null допустим.
Атомарные операции
Главная сила ConcurrentHashMap — атомарные составные операции: computeIfAbsent, merge, compute выполняются атомарно на уровне бакета, заменяя небезопасную связку «проверил-потом-положил» (check-then-act).
Это правильный способ строить, например, конкурентные счётчики или кэши без внешних замков.
// атомарный конкурентный счётчик
counts.merge(key, 1L, Long::sum);
// атомарная ленивая инициализация значения
cache.computeIfAbsent(key, this::loadExpensive);
CopyOnWriteArrayList и BlockingQueue
CopyOnWriteArrayList на каждую запись копирует весь внутренний массив, зато чтения идут без блокировок по неизменяемому снимку. Это идеально для read-mostly данных с редкой записью (списки слушателей/подписчиков) и плохо — для частых изменений.
BlockingQueue (ArrayBlockingQueue — ограниченная на массиве, LinkedBlockingQueue — опционально ограниченная) — основа паттерна producer-consumer: put блокируется на заполненной очереди, take — на пустой, что даёт встроенный backpressure.
Как отвечать на собеседовании
Эталон: ConcurrentHashMap = бакеты HashMap + CAS на пустой бакет и synchronized на голову бакета (а не один глобальный замок), отсюда масштабируемость; null запрещён из-за неоднозначности get; составные операции (computeIfAbsent/merge) атомарны. Добавьте CopyOnWriteArrayList для read-mostly и BlockingQueue для producer-consumer.