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

Является ли HashMap потокобезопасной коллекцией?

2.3 Middle🔥 251 комментариев
#Коллекции#Многопоточность

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

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

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

Ответ: Является ли HashMap потокобезопасной?

Нет, HashMap НЕ является потокобезопасной коллекцией. При одновременном доступе из нескольких потоков может возникнуть состояние гонки (race condition), приводящее к потере данных, бесконечным циклам или некорректной работе.

Почему HashMap не потокобезопасна?

public class HashMap<K,V> {
    transient Node<K,V>[] table;        // Не synchronized
    transient int size;
    transient int modCount;             // Счётчик модификаций
    
    public V put(K key, V value) {
        // Множество операций без синхронизации
        int hash = hash(key);
        int i = (n - 1) & hash;
        // Операция не атомарна - может быть прервана!
    }
}

Проблема: Race Condition

Map<String, Integer> map = new HashMap<>();

Thread t1 = new Thread(() -> {
    for (int i = 0; i < 1_000_000; i++) {
        map.put("key" + i, i);
    }
});

Thread t2 = new Thread(() -> {
    for (int i = 0; i < 1_000_000; i++) {
        map.put("key" + i, i * 2);
    }
});

t1.start();
t2.start();
t1.join();
t2.join();

System.out.println("Размер: " + map.size());  // Может быть < 2000000!

Результат: размер может быть меньше ожидаемого из-за потери данных.

Потокобезопасные альтернативы

1. Collections.synchronizedMap()

Map<String, Integer> syncMap = Collections.synchronizedMap(
    new HashMap<>()
);

// Все операции синхронизированы
syncMap.put("key", 1);
Integer value = syncMap.get("key");

Минус: старая реализация, синхронизирует весь объект целиком.

2. ConcurrentHashMap (ЛУЧШЕ!)

Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();

// Использует bucket-level locking
concurrentMap.put("key1", 1);
concurrentMap.put("key2", 2);

// Несколько потоков могут одновременно писать в разные buckets

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

  • Segment locking: вместо блокировки всей таблицы блокирует только сегмент
  • Лучше масштабируется для параллельного доступа
  • Итератор не throw ConcurrentModificationException (слабо согласован)

3. Hashtable (устаревший подход)

Map<String, Integer> hashtable = new Hashtable<>();

// Старая синхронизация - синхронизирует каждый метод
public synchronized V put(K key, V value) {
    // ...
}

// Плохо для параллелизма - весь объект заблокирован

Сравнение производительности

Map<String, Integer> hashMap = new HashMap<>();
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());

// Тест с 4 потоками, каждый добавляет 1млн элементов
ExecutorService executor = Executors.newFixedThreadPool(4);

// HashMap без синхронизации - БЫСТРО но ОПАСНО
// ConcurrentHashMap - БЫСТРО и БЕЗОПАСНО
// synchronizedMap - медленнее из-за глобальной блокировки

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

// Однопоточное приложение
Map<String, Value> map = new HashMap<>();  // Выбирай это

// Многопоточное приложение
Map<String, Value> map = new ConcurrentHashMap<>();  // Выбирай это!

// Нужна итерация со снимком (редко)
map = Collections.synchronizedMap(new HashMap<>());

// Старый код (не используй в новых проектах)
Hashtable<String, Value> table = new Hashtable<>();  // Забудь об этом

Практический пример

public class UserCache {
    // ПРАВИЛЬНО: потокобезопасная кеш
    private final Map<String, User> cache = new ConcurrentHashMap<>();
    
    public void putUser(String id, User user) {
        cache.put(id, user);
    }
    
    public User getUser(String id) {
        return cache.get(id);
    }
    
    // Можно безопасно читать из разных потоков одновременно
}

Вывод: HashMap - это коллекция для однопоточного использования. Для многопоточных приложений используй ConcurrentHashMap, которая обеспечивает потокобезопасность с лучшей производительностью благодаря сегментной блокировке.