Коллекции в многопоточной среде

Выбор зависит от паттерна чтений, записей и требуемой составной атомарности.

СценарийКоллекция
ключ-значение, частые конкурентные операцииConcurrentHashMap
упорядоченная конкурентная map/setConcurrentSkipListMap/Set
неблокирующая FIFOConcurrentLinkedQueue
producer/consumer с ожиданием и лимитомArrayBlockingQueue, LinkedBlockingQueue
почти только чтение, маленькая коллекцияCopyOnWriteArrayList/Set
простой legacy-доступ под одним lockCollections.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.