Какие потокобезопасные коллекции вы знаете в Java?

«Какие потокобезопасные коллекции вы знаете в Java?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Потокобезопасные коллекции находятся в пакете java.util.concurrent. Их можно разделить на несколько категорий.

1. Высокопроизводительные неблокирующие коллекции Используют механизмы сравнения с обменом (CAS) вместо блокировок (synchronized).

  • ConcurrentHashMap<K,V>: Потокобезопасный аналог HashMap. Обеспечивает высокий параллелизм за счёт внутренней сегментации (в более ранних версиях) или CAS-операций над узлами (Java 8+).
    ConcurrentHashMap<String, Integer> scores = new ConcurrentHashMap<>();
    scores.put("Alice", 100);
    // Потокобезопасные атомарные операции:
    scores.computeIfAbsent("Bob", k -> 0);
    scores.merge("Alice", 50, Integer::sum);
  • ConcurrentLinkedQueue<E>: Неблокирующая очередь на основе связного списка. Потокобезопасные операции offer() (добавление) и poll() (извлечение).
  • ConcurrentSkipListMap<K,V> / ConcurrentSkipListSet<E>: Потокобезопасные аналоги TreeMap и TreeSet, основанные на структуре данных "Skip List". Элементы хранятся в отсортированном порядке.

2. Коллекции с копированием при записи (Copy-On-Write) Идеальны для сценариев, где чтение происходит часто, а запись редко.

  • CopyOnWriteArrayList<E>: При каждой модификации (add, set, remove) создаётся новая копия внутреннего массива. Итераторы работают с "снимком" (snapshot) данных на момент создания и не бросают ConcurrentModificationException.
    CopyOnWriteArrayList<String> logMessages = new CopyOnWriteArrayList<>();
    // Частое чтение многими потоками
    for (String msg : logMessages) { /* Безопасно */ }
    // Редкая запись
    logMessages.add("New log entry"); // Создаёт новую копию массива
  • CopyOnWriteArraySet<E>: Реализация Set на основе CopyOnWriteArrayList.

3. Блокирующие очереди (Blocking Queues) Реализуют интерфейс BlockingQueue. Потоки блокируются при попытке извлечь элемент из пустой очереди или добавить в полную.

  • ArrayBlockingQueue<E>: Очередь на основе массива фиксированной ёмкости (bounded).
  • LinkedBlockingQueue<E>: Очередь на основе связного списка. Может быть как bounded, так и unbounded (по умолчанию Integer.MAX_VALUE).
  • PriorityBlockingQueue<E>: Блокирующая очередь с приоритетами (элементы должны реализовывать Comparable).
    BlockingQueue<Task> taskQueue = new LinkedBlockingQueue<>(100);
    // Производитель (Producer)
    taskQueue.put(new Task()); // Блокируется, если очередь полна
    // Потребитель (Consumer)
    Task task = taskQueue.take(); // Блокируется, если очередь пуста

4. Устаревшие синхронизированные обёртки

  • Коллекции, создаваемые методами Collections.synchronizedList(), synchronizedMap() и т.д.
  • Не рекомендуются для нового кода, так как обеспечивают потокобезопасность за счёт грубой блокировки всего объекта, что сильно снижает производительность при высокой конкуренции. Используйте коллекции из java.util.concurrent.

Выбор коллекции зависит от преобладающей операции (чтение/запись), требований к производительности и необходимости блокировки потоков.