Сначала ответь вслух
Скажи ответ вслух хотя бы пару минут, потом открой эталон и сравни. Эталон открыт всем и не требует входа.
Показать ответ
Эталонный ответ
Collections.synchronizedMap оборачивает каждый метод в synchronized на одном объекте. Потоки выстраиваются в очередь даже на чтение, а обход нужно синхронизировать вручную, иначе возможен ConcurrentModificationException.
- Чтение в ConcurrentHashMap идёт без блокировок.
- Запись в пустую корзину — через CAS, в непустую — с блокировкой только этой корзины.
- Итераторы слабо согласованные: не бросают исключение и могут показать часть изменений, сделанных во время обхода.
putIfAbsent,computeIfAbsent,mergeатомарны.
Map<String, LongAdder> hits = new ConcurrentHashMap<>();
// Неверно: между проверкой и записью успеет другой поток
if (!hits.containsKey(page)) hits.put(page, new LongAdder());
// Верно: одна атомарная операция
hits.computeIfAbsent(page, p -> new LongAdder()).increment();Как у тебя получилось?
Уточняющие вопросы
Так обычно копают глубже. Раскрой строку, когда ответишь вслух.
Уточнение 1-го уровня: Почему ConcurrentHashMap не принимает null?
Ответ уровня Middle
get вернул null: ключа нет или значение null? В одном потоке это проверяют через containsKey, а в многопоточном коде между двумя вызовами карта может измениться. Запрет null убирает эту двусмысленность.
Уточнение 2-го уровня: Что нельзя делать внутри computeIfAbsent?
Ответ уровня Senior
Долго работать: корзина заблокирована, и другие записи в неё ждут. И менять ту же карту: это запрещено документацией и может закончиться IllegalStateException или зависанием.
Типичные ошибки
- Делать проверку и запись двумя вызовами вместо одной атомарной операции.
- Считать size() точным при одновременных изменениях.
Первоисточники
Быстрые задания по теме
Для темы «ConcurrentHashMap» быстрых заданий пока нет
Можно проверить всю зону «Коллекции» тестом на 10 минут.
Проверить себя за 10 минут