Для чего в ConcurrentHashMap используется блокировка по сегментам
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Для чего в 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 позволяет максимизировать параллелизм без потери потокобезопасности. Вместо одной блокировки на весь объект, каждый сегмент имеет свою блокировку, что позволяет разным потокам работать с разными частями структуры одновременно. Это критически важно для высоконагруженных систем.