Ответ
Обе реализации предоставляют потокобезопасный Map, но с принципиально разной архитектурой, что влияет на производительность и поведение.
Collections.synchronizedMap(Map<K,V> m)
- Обертка (Wrapper): Статический метод, который возвращает синхронизированную обертку над переданной обычной
Map(например,HashMap). - Глобальная блокировка: Для обеспечения потокобезопасности весь доступ к карте синхронизируется по одному объекту-монитору (по самой обертке). Это означает, что при любой операции (
get,put,containsKey) блокируется вся карта, даже если потоки работают с разными ключами. - Итераторы: Требуют явной внешней синхронизации на самой карте во время итерации, иначе могут выбросить
ConcurrentModificationException. - Null: Разрешает
nullдля ключей и значений (если их разрешает базовая карта).
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
// Безопасный доступ к отдельным операциям
String value = syncMap.get("key"); // Внутри synchronized
syncMap.put("key", "value"); // Внутри synchronized
// Итерация ТРЕБУЕТ ручной синхронизации
synchronized (syncMap) {
for (Map.Entry<String, String> entry : syncMap.entrySet()) {
// работа с entry
}
}
ConcurrentHashMap
- Специализированная реализация: Построена с нуля для высокой параллельности.
- Сегментированная блокировка (в Java 7) / Блокировки на уровне ячеек (Java 8+): Вместо одной блокировки на всю карту используются множество. В Java 8+ операции на разных ячейках (bucket) часто выполняются без блокировок, используя compare-and-swap (CAS).
- Слабо-согласованные итераторы (weakly-consistent): Итератор отражает состояние карты на момент его создания или обхода, но никогда не бросает
ConcurrentModificationException, даже если карта изменяется во время итерации. Он может показать, а может и не показать последние изменения. - Null: Запрещает
nullдля ключей и значений (чтобы избежать двусмысленности в многопоточном контексте). - Специальные атомарные операции: Предоставляет методы
putIfAbsent(),compute(),merge()и др., которые выполняются атомарно.
ConcurrentHashMap<String, String> concMap = new ConcurrentHashMap<>();
// Высокопроизводительный параллельный доступ
String value = concMap.get("key"); // Часто без блокировок
concMap.put("key", "value"); // Блокировка только на нужном сегменте/ячейке
// Безопасная итерация без внешней синхронизации
for (Map.Entry<String, String> entry : concMap.entrySet()) {
// concMap может изменяться другим потоком - исключения не будет
}
// Атомарная операция
concMap.compute("key", (k, v) -> (v == null) ? "new" : v + "_updated");
Сравнительная таблица
| Критерий | synchronizedMap |
ConcurrentHashMap |
|---|---|---|
| Архитектура | Обертка с глобальной блокировкой | Внутренняя реализация с сегментированными/ячеечными блокировками/CAS |
| Масштабируемость | Низкая (все потоки конкурируют за одну блокировку) | Высокая (потоки работают с разными частями карты параллельно) |
| Итераторы | Требуют внешней синхронизации, иначе ConcurrentModificationException |
Weakly-consistent, безопасны, не бросают исключений |
| Null-значения | Разрешены | Запрещены |
| Атомарные операции | Нет (требуют внешней синхронизации) | Да (putIfAbsent, compute, merge и др.) |
Рекомендация: В современных многопоточных приложениях всегда предпочитайте ConcurrentHashMap. synchronizedMap следует использовать только для простых случаев с низкой конкуренцией или для обеспечения обратной совместимости с устаревшим кодом.