Как получить synchronized коллекцию?
4. Поток B получает доступ
Synchronized коллекция — это обёртка (decorator) вокруг обычной коллекции, которая оборачивает каждый метод в synchronized-блок. Это гарантирует, что одновременно только один поток может вызвать любой метод коллекции.
🟢 Junior Level
Способ через Collections.synchronizedX:
List<String> list = Collections.synchronizedList(new ArrayList<>());
Map<String, Integer> map = Collections.synchronizedMap(new HashMap<>());
Set<String> set = Collections.synchronizedSet(new HashSet<>());
ВАЖНО: итерация требует synchronized!
// ❌ Неправильно
for (String s : syncList) { ... }
// ✅ Правильно
synchronized (syncList) {
for (String s : syncList) { ... }
}
🟡 Middle Level
Фабричные методы
Collections.synchronizedList(list)
Collections.synchronizedMap(map)
Collections.synchronizedSet(set)
Collections.synchronizedNavigableMap(navigableMap) // Java 8+
Механика: Decorator паттерн
// Внутри:
class SynchronizedList<E> implements List<E> {
final List<E> list;
final Object mutex; // = this по умолчанию
public synchronized E get(int index) {
return list.get(index); // synchronized на mutex
}
}
Что происходит при конкурентном доступе:
- Поток A вызывает
get(0)→ захватывает мониторmutex - Поток B вызывает
get(1)→ блокируется наsynchronized, ждёт - Поток A завершает → освобождает монитор
- Поток B получает доступ
Сравнение с альтернативами
| Способ | Когда |
|---|---|
| Collections.synchronizedX | Полная блокировка нужна |
| ConcurrentHashMap | Highload, частые записи |
| CopyOnWriteArrayList | Списки слушателей |
| Vector/Hashtable | НИКОГДА (legacy) |
Типичные ошибки
// 1. Забытый synchronized при итерации
for (var e : syncList) { ... } // CME!
// 2. Экспорт оригинала
List original = new ArrayList<>();
List sync = Collections.synchronizedList(original);
original.add("x"); // Thread-safety нарушена!
// 3. Двойная синхронизация
synchronized (syncList) {
syncList.add(e); // Внутренний + внешний synchronized
}
🔴 Senior Level
Mutex object
// По умолчанию mutex (mutual exclusion — объект-ключ для synchronized) = this (обёртка)
// → Все synchronized методы блокируют друг друга
// backing collection — оригинальная коллекция, которую оборачивает synchronized-обёртка
// Можно (через рефлексию) установить свой mutex
// → Но JDK API не поддерживает напрямую
Production Experience
Почему ConcurrentHashMap лучше:
synchronizedMap:
→ 1 лок на всё
→ Чтение блокирует чтение
ConcurrentHashMap:
→ Lock-free чтение
→ CAS для записи
→ Гранулярные локи на корзину
Best Practices
- Итерация → synchronized блок
- Не экспортируйте оригинал
- ConcurrentHashMap предпочтительнее
- mutex = this по умолчанию
- Избегайте двойной синхронизации
Резюме для Senior
- Decorator паттерн → synchronized на каждый метод
- Итерация → ручной synchronized блок
- mutex = this → полная блокировка
- ConcurrentHashMap > synchronizedMap
- Не экспортируйте backing collection (оригинал, который оборачивается)
🎯 Шпаргалка для интервью
Обязательно знать:
Collections.synchronizedList/Set/Map— фабричные методы-обёртки (Decorator паттерн)- Каждая синхронизируется на одном
mutexобъекте (по умолчаниюthis) - Итерация обязательно требует
synchronized(list) { ... }блока - Оригинальная коллекция остаётся доступной — изменение через неё нарушает thread-safety
- Двойная синхронизация (
synchronized(syncList) { syncList.add(e); }) — лишний overhead - Альтернативы: ConcurrentHashMap (highload), CopyOnWriteArrayList (списки слушателей)
- Vector/Hashtable — legacy, НИКОГДА в новом коде
Частые уточняющие вопросы:
- Что такое mutex в synchronized коллекциях? — Объект-ключ для
synchronized; по умолчаниюthis, все методы блокируют друг друга. - Почему итерация по synchronizedList без synchronized блока приводит к CME? — Итератор вызывает hasNext/next отдельно; между ними другой поток может изменить коллекцию.
- Чем Collections.synchronizedList отличается от ConcurrentHashMap? — Первый — один лок на всё; второй — lock-free чтение + гранулярные локи.
- Что такое backing collection? — Оригинальная коллекция, которую оборачивает synchronized-обёртка; доступ к ней в обход обёртки ломает thread-safety.
Красные флаги (НЕ говорить):
- «Можно менять оригинальную коллекцию без последствий» — это нарушает thread-safety
- «synchronizedList гарантирует атомарность составных операций» — нет, нужен внешний synchronized
- «Iterator.remove() работает без synchronized для synchronizedList» — итерация всегда требует ручной блокировки
- «Vector — современная альтернатива» — это legacy с грубой блокировкой
Связанные темы:
- [[21. Когда использовать synchronized collections]]
- [[23. Что такое Collections.unmodifiableList()]]
- [[25. В чём разница между Iterator и ListIterator]]