← Назад к вопросам

Для чего в ConcurrentHashMap используется блокировка по сегментам

3.0 Senior🔥 111 комментариев
#Коллекции#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Для чего в ConcurrentHashMap используется блокировка по сегментам

Блокировка по сегментам (bucket-level locking) в ConcurrentHashMap нужна для обеспечения высокого уровня параллелизма при сохранении потокобезопасности.

Проблема с синхронизацией весь объект

Eсли использовать одну глобальную блокировку на весь HashMap:

// Проблемный подход
public class SynchronizedHashMap {
    private HashMap<String, Integer> map = new HashMap<>();
    
    // Одна блокировка на весь объект
    public synchronized void put(String key, Integer value) {
        map.put(key, value);
    }
    
    public synchronized Integer get(String key) {
        return map.get(key);
    }
}

Недостаток: если два потока пытаются обновить РАЗНЫЕ ячейки, они всё равно блокируют друг друга. Это убивает производительность на многопроцессорных системах.

Решение: блокировка по сегментам

ConcurrentHashMap разделяет хеш-таблицу на N сегментов (buckets). Каждый сегмент имеет свою собственную блокировку.

ConcurrentHashMap с 4 сегментами:

┌─────────────────────────────────────┐
│ Segment 0 (Lock 0) │ Hash % 4 == 0  │
├─────────────────────────────────────┤
│ Segment 1 (Lock 1) │ Hash % 4 == 1  │
├─────────────────────────────────────┤
│ Segment 2 (Lock 2) │ Hash % 4 == 2  │
├─────────────────────────────────────┤
│ Segment 3 (Lock 3) │ Hash % 4 == 3  │
└─────────────────────────────────────┘

Поток 1: ставит key1 в Segment 0
Поток 2: ставит key2 в Segment 2
↓
Они работают ПАРАЛЛЕЛЬНО (разные блокировки)!

Поток 3: ставит key3 в Segment 0
Поток 4: пытается получить key1 из Segment 0
↓
Они блокируют друг друга (одна блокировка)

Как это работает

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(16);

// Пример операций
map.put("alice", 100);   // Хешируется в сегмент 0
map.put("bob", 200);     // Хешируется в сегмент 3
map.put("charlie", 300); // Хешируется в сегмент 0

// Поток 1 обновляет alice (сегмент 0 заблокирован)
// Поток 2 обновляет bob (сегмент 3 заблокирован)
// ↓
// Оба потока работают одновременно!

Сравнение подходов

ПодходПотокобезопасностьПроизводительностьСложность
HashMap + synchronized✓ Да✗ ПлохаяПростая
Collections.synchronizedMap✓ Да✗ ПлохаяПростая
ConcurrentHashMap (сегменты)✓ Да✓ ОтличнаяСредняя
ReadWriteLockЗависитСредняяСложная

Пример: многопоточность

ConcurrentHashMap<String, Integer> map = 
    new ConcurrentHashMap<>(16);

// Поток 1: операции с ключами, которые попадают в сегмент 0
Thread thread1 = new Thread(() -> {
    for (int i = 0; i < 1000; i++) {
        map.put("key_0_" + i, i);
    }
});

// Поток 2: операции с ключами, которые попадают в сегмент 1
Thread thread2 = new Thread(() -> {
    for (int i = 0; i < 1000; i++) {
        map.put("key_1_" + i, i);
    }
});

thread1.start();
thread2.start();
thread1.join();
thread2.join();

// Оба потока работали параллельно!
System.out.println("Total entries: " + map.size()); // 2000

Java 8+: изменения

Начиная с Java 8, ConcurrentHashMap использует node-level locking вместо segment-level:

// Java 8+ использует synchronized на узлах
private static class Node<K, V> {
    volatile int hash;
    volatile K key;
    volatile V val;
    volatile Node<K, V> next;
    
    // Синхронизация на уровне отдельных узлов
}

Это даже лучше, чем сегменты, потому что:

  • Более гранулярная блокировка
  • Лучше использует современные процессоры

Ключевые преимущества

Высокий параллелизм — несколько потоков могут работать с разными сегментами одновременно

Потокобезопасность — каждая операция атомарна для своего сегмента

Масштабируемость — чем больше сегментов, тем больше потоков может работать параллельно

Никаких deadlock-ов — каждый сегмент имеет одну блокировку

Сравнение с synchronized

// Медленно: одна блокировка для всех операций
Map<String, Integer> syncMap = 
    Collections.synchronizedMap(new HashMap<>());

// Быстро: блокировка только на нужный сегмент
ConcurrentHashMap<String, Integer> concMap = 
    new ConcurrentHashMap<>();

Вывод

Блокировка по сегментам в ConcurrentHashMap позволяет максимизировать параллелизм без потери потокобезопасности. Вместо одной блокировки на весь объект, каждый сегмент имеет свою блокировку, что позволяет разным потокам работать с разными частями структуры одновременно. Это критически важно для высоконагруженных систем.