Как ConcurrentHashMap в Java обеспечивает потокобезопасность?

«Как ConcurrentHashMap в Java обеспечивает потокобезопасность?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

ConcurrentHashMap (CHM) обеспечивает высокую степень параллелизма за счет сегментирования блокировок (lock striping) и безблокировочных операций.

Основные механизмы (эволюция):

  • До Java 8: Внутренняя таблица делилась на сегменты (Segment), каждый со своей блокировкой. Потоки, работающие с разными сегментами, не блокировали друг друга.
  • Java 8+: Используется более оптимизированный подход:
    1. Блокировка на уровне узла (Node): Вместо сегментов блокируется только голова отдельного бакета (связного списка или дерева) при необходимости.
    2. CAS-операции (Compare-And-Swap): Для основных операций (например, вставка в пустой бакет) используются атомарные операции sun.misc.Unsafe, что позволяет обойтись без блокировок.
    3. Синхронизированные методы для отдельных операций: Критические секции минимальны и используют synchronized.

Пример и преимущества:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Потокобезопасные атомарные операции:
map.put("key", 1);
map.compute("key", (k, v) -> v + 1); // Атомарное обновление
Integer val = map.get("key"); // Чтение без блокировки

Ключевые отличия от Hashtable:

  • Высокий параллелизм: Чтение почти никогда не блокируется, запись блокирует только отдельный бакет.
  • Слабосогласованная итерация (weakly-consistent): Итератор отражает состояние мапы на момент его создания, но может не показывать последующие изменения.
  • Отсутствие глобальной блокировки (lock-free для чтения).