В чём разница между ConcurrentHashMap и Collections.synchronizedMap в Java

«В чём разница между ConcurrentHashMap и Collections.synchronizedMap в Java» — вопрос из категории Многопоточность, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Обе структуры обеспечивают потокобезопасность, но с фундаментально разной архитектурой, что влияет на производительность и поведение.

Основные различия

  1. Гранулярность блокировки

    • ConcurrentHashMap: Использует сегментированные блокировки (в старых версиях) или оптимистичные блокировки (CAS-операции) на уровне отдельных бакетов (в Java 8+). Это позволяет множеству потоков параллельно читать и даже записывать в разные сегменты.
    • Collections.synchronizedMap(Map): Создает обертку, которая синхронизирует все методы исходной мапы с помощью одной общей блокировки (synchronized). На время любой операции (даже get()) вся мапа блокируется для других потоков.
  2. Согласованность итераторов

    • ConcurrentHashMap: Итераторы обладают слабой согласованностью (weakly consistent). Они не бросают ConcurrentModificationException и могут отражать часть, но не обязательно все изменения, произошедшие после их создания.
    • synchronizedMap: Для безопасного перебора требуется явная внешняя синхронизация на самом объекте мапы, иначе возможно исключение ConcurrentModificationException.

Пример использования

// ConcurrentHashMap — высокая производительность при конкурентном доступе
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put("key", 42); // Не требует внешней синхронизации
Integer value = concurrentMap.get("key");

// synchronizedMap — простое, но менее производительное решение
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());

// Для итерации все равно нужна ручная синхронизация
synchronized (syncMap) {
    for (Map.Entry<String, Integer> entry : syncMap.entrySet()) {
        // обработка entry
    }
}

Когда что использовать?

  • ConcurrentHashMap: Практически всегда для высоконагруженных многопоточных приложений. Это стандартный выбор.
  • Collections.synchronizedMap(): Только если нужна простая синхронизация для мапы с очень низкой конкуренцией записей или для совместимости с кодом, требующим полной синхронизации итераторов.