Коллекции в многопоточной среде
Выбор зависит от паттерна чтений, записей и требуемой составной атомарности.
| Сценарий | Коллекция |
|---|---|
| ключ-значение, частые конкурентные операции | ConcurrentHashMap |
| упорядоченная конкурентная map/set | ConcurrentSkipListMap/Set |
| неблокирующая FIFO | ConcurrentLinkedQueue |
| producer/consumer с ожиданием и лимитом | ArrayBlockingQueue, LinkedBlockingQueue |
| почти только чтение, маленькая коллекция | CopyOnWriteArrayList/Set |
| простой legacy-доступ под одним lock | Collections.synchronized* |
ConcurrentSkipListMap основана на skip list, а не на hash table. Ее операции обычно O(log n) и сохраняют сортировку.
Составные операции
ConcurrentMap<String, Value> cache = new ConcurrentHashMap<>();
Value value = cache.computeIfAbsent(key, this::load);containsKey() затем put() не образуют атомарную комбинацию. Нужны putIfAbsent, compute, merge либо внешний lock для более широкого инварианта.
Функции compute* должны быть короткими и без рекурсивного изменения той же map: реализация может удерживать внутреннюю синхронизацию.
Итераторы
- synchronized wrapper требует внешнего
synchronized(collection)на весь обход; - concurrent-коллекции обычно дают weakly consistent iterator: без
ConcurrentModificationException, но не единый snapshot; - copy-on-write iterator видит immutable snapshot момента создания.
Copy-on-write копирует весь массив при каждой модификации, поэтому плох для частых записей или больших списков.
На собеседовании
- Thread-safe ли
containsKey+put? Нет как единая операция. - Нужно ли блокировать synchronizedList при iteration? Да.
- Что видит iterator ConcurrentHashMap? Слабосогласованное представление, не обязательно snapshot.
- Когда CopyOnWrite выгоден? Редкие записи, частые обходы.
- ConcurrentSkipListMap — hash table? Нет, упорядоченная skip list.