← Назад к вопросам
Как создать синхронизированную коллекцию?
1.0 Junior🔥 171 комментариев
#Коллекции#Многопоточность#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как создать синхронизированную коллекцию
Синхронизированная коллекция — это коллекция, которая потокобезопасна при одновременном доступе из нескольких потоков. В Java есть несколько подходов.
Способ 1: Collections.synchronized*()
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
// Синхронизированный List
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.add("item1");
syncList.add("item2");
// Синхронизированный Set
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
syncSet.add("item1");
// Синхронизированный Map
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
syncMap.put("key", 1);
Как это работает:
// Внутри Collections.synchronizedList() создаётся wrapper
public static <T> List<T> synchronizedList(List<T> list) {
return new SynchronizedList<>(list);
}
// SynchronizedList оборачивает каждый метод
private static class SynchronizedList<E> extends SynchronizedCollection<E>
implements List<E> {
private final List<E> list; // Оригинальный список
@Override
public void add(int index, E element) {
synchronized(mutex) { // Синхронизация по мьютексу
list.add(index, element);
}
}
@Override
public E get(int index) {
synchronized(mutex) {
return list.get(index);
}
}
}
Проблема: итерация НЕ потокобезопасна
// ❌ ОПАСНО: ConcurrentModificationException
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
for (String item : syncList) { // Начало итерации
System.out.println(item);
// Из другого потока: syncList.add("new"); ConcurrentModificationException!
}
// ✅ ПРАВИЛЬНО: явная синхронизация при итерации
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
synchronized(syncList) { // Заморозили список
for (String item : syncList) {
System.out.println(item);
}
} // Разморозили
Способ 2: Explicit Locking (Lock)
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.ArrayList;
import java.util.List;
public class SynchronizedListWithLock<T> {
private final List<T> list = new ArrayList<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// Несколько читателей одновременно
public T get(int index) {
lock.readLock().lock();
try {
return list.get(index);
} finally {
lock.readLock().unlock();
}
}
// Только один писатель
public void add(T element) {
lock.writeLock().lock();
try {
list.add(element);
} finally {
lock.writeLock().unlock();
}
}
public int size() {
lock.readLock().lock();
try {
return list.size();
} finally {
lock.readLock().unlock();
}
}
}
// Использование
SynchronizedListWithLock<String> list = new SynchronizedListWithLock<>();
list.add("item1");
String item = list.get(0);
int size = list.size();
Преимущества ReadWriteLock:
- Несколько читателей могут читать одновременно
- Писатель исключает всех (читателей и других писателей)
- Лучше производительность для read-heavy операций
Способ 3: ConcurrentHashMap (для Map)
import java.util.concurrent.ConcurrentHashMap;
// ConcurrentHashMap — лучше, чем synchronized Map
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key1", 1);
map.put("key2", 2);
// Итерация потокобезопасна (снимок состояния)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// Атомарные операции
map.putIfAbsent("key1", 10); // Не перезапишет, если уже есть
map.replace("key1", 1, 100); // Заменит только если значение == 1
Как работает:
// ConcurrentHashMap использует bucket-level locking
// Вместо одного мьютекса для всей карты,
// есть несколько мьютексов для разных «сегментов»
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// По умолчанию 16 сегментов
// Каждый сегмент защищён отдельным Lock'ом
// Потоки могут писать в разные сегменты параллельно
// Поток 1: map.put("a", 1) → Lock сегмента 0
// Поток 2: map.put("b", 2) → Lock сегмента 1
// Могут выполняться параллельно!
Способ 4: CopyOnWriteArrayList (для List)
import java.util.concurrent.CopyOnWriteArrayList;
List<String> list = new CopyOnWriteArrayList<>();
list.add("item1");
list.add("item2");
// Итерация очень быстра
for (String item : list) {
System.out.println(item);
}
// Модификация медленнее (копирует весь массив)
list.add("item3"); // Копирует старый + новый элемент
list.remove(0); // Копирует без элемента 0
Как работает:
// При каждой модификации копируется весь массив
public class CopyOnWriteArrayList<E> {
private volatile Object[] array;
private final ReentrantLock lock = new ReentrantLock();
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// Копируем старый массив + новый элемент
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
public E get(int index) {
// НЕ нужна синхронизация при чтении
return (E) getArray()[index];
}
}
Когда использовать CopyOnWriteArrayList:
- Много читаний, мало модификаций (write-rarely)
- Малое количество элементов
- Итераторы не должны видеть новые элементы
Способ 5: ConcurrentLinkedQueue (для Queue)
import java.util.concurrent.ConcurrentLinkedQueue;
// Потокобезопасная очередь без блокировок (lock-free)
Queue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("item1");
queue.offer("item2");
String item = queue.poll(); // Получить и удалить
String peek = queue.peek(); // Получить без удаления
// Итерация потокобезопасна
for (String item : queue) {
System.out.println(item);
}
Сравнительная таблица
| Коллекция | Метод синхронизации | Итерация | Производительность |
|---|---|---|---|
synchronizedList() | One lock | ❌ Опасна | Низкая |
CopyOnWriteArrayList | Copy-on-write | ✅ Безопасна | Низкая на write |
ConcurrentHashMap | Segment locks | ✅ Безопасна | Высокая |
ConcurrentLinkedQueue | Lock-free | ✅ Безопасна | Очень высокая |
| Custom with ReadWriteLock | ReadWriteLock | ✅ Безопасна | Зависит от коэфф. |
Рекомендации
// ✅ Для Map — используй ConcurrentHashMap
Map<String, Integer> map = new ConcurrentHashMap<>();
// ✅ Для List с много модификаций — synchronized
List<String> list = Collections.synchronizedList(new ArrayList<>());
// ✅ Для List с мало модификаций — CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
// ✅ Для Queue — ConcurrentLinkedQueue
Queue<String> queue = new ConcurrentLinkedQueue<>();
// ✅ Для Custom logic — ReentrantReadWriteLock
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock(); // Читают много
lock.writeLock().lock(); // Пишет редко
Итоги
- Collections.synchronized()* — простой способ, но медленный
- ConcurrentHashMap — лучше для Map, lock-free
- CopyOnWriteArrayList — для read-heavy List
- ConcurrentLinkedQueue — для очереди, очень быстро
- ReadWriteLock — для кастомной логики
- Всегда тестируй под нагрузкой в многопоточной среде
- Избегай synchronized(collection) при итерации — замораживает весь список