Короткий ответ
Синхронизированные коллекции — это обёртки над обычными коллекциями, создаваемые через Collections.synchronizedList(), synchronizedMap() и т. п., а также исторические Vector и Hashtable: в них каждый публичный метод синхронизирован целиком, все операции захватывают один общий монитор коллекции. Concurrent-коллекции из java.util.concurrent (ConcurrentHashMap, CopyOnWriteArrayList и др.) спроектированы для высокой конкуренции: они используют гранулярную синхронизацию, CAS и слабо консистентные итераторы.
Как это работает подробнее
Synchronized-коллекции гарантируют потокобезопасность, но создают сильную конкуренцию: чем больше потоков обращается одновременно, тем больше времени они проводят в ожидании общей блокировки, поэтому масштабируемость плохая. Итерироваться по ним нужно внутри собственного блока synchronized, иначе поведение при параллельном изменении недетерминировано.
ConcurrentHashMap в современных версиях синхронизирует доступ по корзинам таблицы: операции над разными корзинами не мешают друг другу, при вставке в пустую корзину применяется CAS без блокировки, а чтение в большинстве случаев вообще не блокируется.
- Итераторы concurrent-коллекций слабо консистентные (weakly consistent): не бросают ConcurrentModificationException и отражают состояние на момент обхода, но не гарантируют снимок.
- CopyOnWriteArrayList копирует весь массив при каждой модификации: читатели не блокируются и всегда видят консистентное состояние — это идеально при чтениях, значительно превышающих записи.
Пример кода
import java.util.*;
import java.util.concurrent.*;
public class CollectionsComparison {
public static void main(String[] args) {
// synchronized-обёртка: каждый метод под общим монитором
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.add("a");
// concurrent-коллекция: гранулярная синхронизация
Map<String, String> map = new ConcurrentHashMap<>();
map.put("key", "value");
// CopyOnWriteArrayList: копирование массива при записи
List<String> cow = new CopyOnWriteArrayList<>();
cow.add("b");
}
}Что использовать на практике
- В однопоточном коде используйте обычные коллекции — синхронизация не нужна.
- Для простой потокобезопасности с редкими обращениями подойдут synchronized-обёртки.
- Для высоконагруженного многопоточного доступа выбирайте ConcurrentHashMap, а для сценария «много чтений, мало записей» — CopyOnWriteArrayList.
Подводные камни
- Итерация по synchronized-коллекции без собственного блока synchronized небезопасна при параллельном изменении.
- Synchronized-коллекции плохо масштабируются из-за единого монитора, а CopyOnWriteArrayList дорог при частых записях из-за копирования массива.
- Слабо консистентные итераторы не дают снимка состояния: результат обхода может отличаться от момента его начала.
Как отвечать на собеседовании
- Противопоставьте подход «один монитор на метод» и гранулярную синхронизацию → назовите примеры классов → объясните слабо консистентные итераторы и CAS → отдельно разберите CopyOnWriteArrayList → дайте рекомендацию по выбору под нагрузку.