Несколько потоков одновременно пишут в обычную HashMap. Чем это грозит?
- Потокобезопасен ли HashMap?
- Чем ConcurrentHashMap отличается от Collections.synchronizedMap?
HashMap не потокобезопасен: параллельная запись ломает структуру — потерянные записи, неверный size, в старых версиях — зависание обхода.
- Ошибка проявляется не сразу и далеко от места проблемы
- Решение — ConcurrentHashMap; synchronizedMap проще, но одна блокировка на всё
А ещё Даже потокобезопасная карта не спасает составные операции «проверь, затем положи» — нужны compute/merge/putIfAbsent
Подробно
HashMap не рассчитан на одновременную запись из нескольких потоков. Два потока могут одновременно вставлять в одну корзину или запустить расширение таблицы — и одна из записей потеряется, size() станет неверным. В Java 7 и раньше параллельное расширение могло зациклить список в корзине, и обход зависал навсегда.
Коварство в том, что исключения обычно нет: данные просто оказываются неверными, и это обнаруживается далеко от места проблемы.
Решения:
- **
ConcurrentHashMap** — спроектирован для параллельного доступа: чтение без блокировок, запись блокирует только конкретную корзину; - **
Collections.synchronizedMap** — обёртка с одной блокировкой на всю карту: проще, но потоки ждут друг друга; итерировать нужно, вручную синхронизируясь на карте.
Отдельная ловушка — составные операции. Код «если ключа нет — положить» из двух вызовов не атомарен даже на потокобезопасной карте: между проверкой и вставкой вмешается другой поток. Нужны атомарные методы: putIfAbsent, computeIfAbsent, merge.
Пример
Map<String, Integer> counts = new ConcurrentHashMap<>();
// counts.put(word, counts.getOrDefault(word, 0) + 1); // гонка
counts.merge(word, 1, Integer::sum); // атомарноЧто хотят услышать
- HashMap не потокобезопасен, последствия параллельной записи.
- ConcurrentHashMap против synchronizedMap.
- Неатомарность составных операций и атомарные методы.
Частые ошибки
- Считать отсутствие исключения признаком корректной работы.
- Полагаться на синхронизирующую обёртку при составных операциях.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Будет брошено ConcurrentModificationException при первой же параллельной записи
- ✗Ничего: карта синхронизирована по умолчанию
- ✗Записи просто выполнятся медленнее
- ✓Порчей структуры: потерянные записи, неверный size
Что спросят дальше
- Чем конкурентная реализация лучше синхронизирующей обёртки?
- Почему в конкурентной карте запрещены null?
Разбор темы
- Конкурентные коллекции
ConcurrentHashMap без сегментов (CAS + блокировка бакета), запрет null, CopyOnWriteArrayList и BlockingQueue.
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.