Як отримати 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]]