Collections.synchronizedMap: примеры (JAVA)
Collections.synchronizedMap(Map m): Map Описание и поведение Collections.synchronizedMap
Метод Collections.synchronizedMap предоставляет потокобезопасную оболочку вокруг существующей реализации интерфейса Map. Подпись метода: public static . Входной параметр m - ссылка на исходную карту, которая будет использоваться как хранилище данных. При передаче null будет выброшено java.lang.NullPointerException.
Возвращаемое значение - объект Map, реализующий ту же «логику хранения», что и исходная карта, но все публичные методы которого синхронизированы на общем мьютексе. Это означает, что при вызове методов, таких как get, put, remove, containsKey и др., используется монитор (lock) для последовательного доступа потоков. Возвращаемая карта является представлением (view) исходной карты: изменения в исходной коллекции отражаются в возвращаемой и наоборот.
Особенности поведения и ограничения:
- Мьютекс: у возвращаемой обёртки используется внутренний объект-мьютекс. В стандартной реализации Collections.synchronizedMap мьютекс - сам объект оболочки, поэтому при внешней синхронизации требуется блокировать именно обёртку: synchronized(synchronizedMap) { ... }.
- Итераторы: итераторы, полученные от возвращаемой карты (entrySet().iterator(), keySet().iterator(), values().iterator()), не синхронизированы автоматически. Для безопасной итерации нужно явно синхронизировать блок на обёртке: synchronized(map) { for (Iterator i = map.entrySet().iterator(); i.hasNext();) ... }.
- Сериализация: возвращаемая карта сериализуема, если сериализуема исходная карта.
- Производительность: синхронизированная обёртка использует один глобальный мьютекс для всех операций, что может стать узким местом при высокой многопоточности. Для высоконагруженных сценариев обычно предпочтителен ConcurrentHashMap.
- Поддержка null: способность хранить null-ключи и значения определяется исходной картой. Например, HashMap допускает null, ConcurrentHashMap - не допускает.
Типичные сигнатуры и эффекты:
Collections.synchronizedMap(Map<K,V> m)- возвращает потокобезопасную обёртку; выбрасывает NullPointerException при m == null.
Примеры базового применения synchronizedMap
Создание обёртки и базовые операции.
Map<String,Integer> raw = new HashMap<>();
Map<String,Integer> sync = Collections.synchronizedMap(raw);
sync.put("a", 1);
sync.put("b", 2);
System.out.println(sync.get("a"));
1
Итерация с внешней синхронизацией - обязательна для корректности.
Map<String,Integer> m = Collections.synchronizedMap(new HashMap<>());
m.put("x", 10); m.put("y", 20);
// Неправильно: возможны проблемы при одновременном доступе
for (Map.Entry<String,Integer> e : m.entrySet()) {
System.out.println(e.getKey()+":"+e.getValue());
}
// Правильно
synchronized(m) {
for (Map.Entry<String,Integer> e : m.entrySet()) {
System.out.println(e.getKey()+":"+e.getValue());
}
}
x:10 y:20
Сравнение с ConcurrentHashMap: синхронизированная обёртка блокирует все операции, ConcurrentHashMap обеспечивает более высокую пропускную способность для чтений и частичных записей.
Аналоги в Java и их отличия
- ConcurrentHashMap - конкурентная реализация Map, не использующая один глобальный мьютекс; предпочтительна при высокой конкуренции потоков и частых операциях чтения.
- Collections.synchronizedSortedMap и Collections.synchronizedNavigableMap - синхронизированные обёртки для SortedMap и NavigableMap соответственно; применяются, если нужен упорядоченный функционал с синхронизацией.
- Hashtable - устаревшая коллекция, все публичные методы синхронизированы; содержит наследие API и обычно заменяется на ConcurrentHashMap или synchronizedMap при совместимости.
Аналоги в других языках и отличия
Краткий обзор альтернатив на популярных языках с примерами.
- Python: стандартный dict не синхронизирован на уровне множественных потоков; для потокобезопасности используют threading.Lock или collections.OrderedDict вместе с блокировкой. Пример:
from threading import Lock
m = {}
lock = Lock()
with lock:
m['a'] = 1
print(m['a'])
1
- JavaScript (Node.js): в основном однопоточная модель, Map не синхронизируется; для Worker Threads и общего состояния применяют внешние механизмы синхронизации или обмен сообщениями.
- PHP: нет встроенных потокобезопасных карт в стандартном режиме; в многопоточных расширениях используются инструменты вроде pthreads или системное кеширование (Redis, Memcached).
- C#: ConcurrentDictionary как прямой аналог ConcurrentHashMap; есть Hashtable.Synchronized возвращающий обёртку, похожую на Collections.synchronizedMap.
- Go: встроенная map не безопасна для конкурентного доступа; стандартная альтернатива - sync.Map для специфичных задач или map + sync.RWMutex для общей гибкости.
- Kotlin: использует Java-коллекции; применимы те же варианты: Collections.synchronizedMap и ConcurrentHashMap.
- Lua: стандартные таблицы не синхронизированы; в многопоточных средах применяются внешние библиотеки или очереди сообщений между состояниями.
- SQL: концептуально похожие потребности решаются транзакциями и блокировкой на уровне базы данных, что отличается от объектной синхронизации в памяти.
Кодовые примеры для Go и C#:
// Go: использование sync.Map
var m sync.Map
m.Store("k", 42)
v, _ := m.Load("k")
fmt.Println(v)
42
// C#: ConcurrentDictionary
var d = new System.Collections.Concurrent.ConcurrentDictionary();
d["k"] = 5;
Console.WriteLine(d["k"]);
5
Типичные ошибки и неочевидные проблемы
- Отсутствие синхронизации при итерации: получение итератора и обход без synchronized(wrapper) может привести к ConcurrentModificationException или некорректному результату в многопоточной среде. Пример неправильного кода:
Map<Integer,String> m = Collections.synchronizedMap(new HashMap<>());
m.put(1, "a");
for (Map.Entry<Integer,String> e : m.entrySet()) {
m.put(2, "b"); // модификация во время итерации
}
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1598)
...
- Синхронизация не на той инстанции: некоторые ожидают, что нужно синхронизировать на исходном raw-объекте; правильное - синхронизировать на возвращаемой обёртке. Иначе гарантии не выполняются.
- Ожидание производительности: использование synchronizedMap в сценариях с большим количеством потоков и операциями чтения/записи может привести к узкому месту. В таких случаях ConcurrentHashMap или специализированные структуры предпочтительнее.
- Null-ключи и значения: поведение зависит от базовой реализации; при замене на ConcurrentHashMap код может неожиданно начать бросать NullPointerException.
Изменения и история метода
Метод Collections.synchronizedMap присутствует в Java с ранних версий (коллекции были введены в JDK 1.2). В базовом поведении изменений немного: метод по-прежнему возвращает синхронизированную обёртку вокруг переданной карты. В более новых релизах Java появились дополнительные синхронизированные обёртки для SortedMap и NavigableMap, а также усовершенствования в других коллекциях. Для конкурентных сценариев рекомендуются структуры из java.util.concurrent (ConcurrentHashMap и др.), которые развивались и оптимизировались в более поздних версиях JVM.
Расширенные и необычные примеры использования
1) Обёртка для LRU-кэша на базе LinkedHashMap. При использовании в многопоточной среде обёртка обеспечивает синхронизацию, однако важно помнить о synchronize при итерации.
Map<String,Integer> lru = Collections.synchronizedMap(
new LinkedHashMap<String,Integer>(16, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<String,Integer> eldest) {
return size() > 3;
}
}
);
// Заполнение
lru.put("a",1); lru.put("b",2); lru.put("c",3); lru.put("d",4);
// Содержимое
synchronized(lru) {
for (Map.Entry<String,Integer> e : lru.entrySet()) {
System.out.println(e.getKey());
}
}
b c d
Пояснение: при ограничении размера 3 самый старый элемент удалён. Доступ к map обёрнут синхронизацией перед итерацией.
2) Совместное использование с внешним мьютексом. В стандартной коллекции мьютекс - сама обёртка. Дальше пример, как создать обёртку с общим мьютексом вручную (внутри класса):
Map<String,Integer> base = new HashMap<>();
Object mutex = new Object();
Map<String,Integer> custom = new AbstractMap<>() {
public Set<Entry<String,Integer>> entrySet() { return base.entrySet(); }
public Integer get(Object k) { synchronized(mutex) { return base.get(k); } }
public Integer put(String k, Integer v) { synchronized(mutex) { return base.put(k,v); } }
};
// Теперь можно синхронизировать на mutex во всех смежных структурах
(нет вывода, демонстрация идеи)
Пояснение: при наличии нескольких связанных структур данных можно синхронизировать их на одном объекте для атомарных комплексных операций.
3) Обёртка вокруг TreeMap с навигацией (synchronizedSortedMap):
SortedMap<Integer,String> sm = Collections.synchronizedSortedMap(new TreeMap<>());
sm.put(1,"one"); sm.put(2,"two");
synchronized(sm) {
System.out.println(sm.firstKey());
}
1
4) Нежелательная практика: ожидание освобождения внешнего lock при блокировке внутри synchronizedMap. При вызове пользовательского кода изнутри синхронизированного метода есть риск дедлока, если этот код попытается захватить другие внешние ресурсы.
5) Пример взаимодействия с ConcurrentHashMap: переключение с synchronizedMap на ConcurrentHashMap может потребовать изменений в коде, если раньше использовалась внешняя синхронизация для итерации, так как ConcurrentHashMap возвращает слабоконсистентные итераторы, не выбрасывающие ConcurrentModificationException.
Map<String,Integer> sync = Collections.synchronizedMap(new HashMap<>());
Map<String,Integer> conc = new ConcurrentHashMap<>();
// При использовании conc итерация не требует внешней блокировки
for (Map.Entry<String,Integer> e : conc.entrySet()) {
// возможны несогласованные, но безопасные снимки данных
}
(нет определённого вывода, зависит от состояния conc)
джава Collections.synchronizedMap function comments
- джава Collections.synchronizedMap - аргументы и возвращаемое значение
- Функция java Collections.synchronizedMap - описание
- Collections.synchronizedMap - примеры
- Collections.synchronizedMap - похожие методы на java
- Collections.synchronizedMap на javascript, c#, python, php
- Collections.synchronizedMap изменения java
- Примеры Collections.synchronizedMap на джава