Какие concurrent collections в Java ты использовал и в чём их особенности?

Ответ

Concurrent Collections из пакета java.util.concurrent предоставляют потокобезопасные реализации стандартных интерфейсов коллекций, оптимизированные для многопоточного доступа.

Основные классы и их применение:

  1. ConcurrentHashMap<K, V>

    • Особенность: Вместо блокировки всей таблицы использует блокировку на уровне сегментов (бакетов) или CAS-операции (в Java 8+).
    • Итераторы: Обход происходит с weak consistency — могут не отражать последние изменения, но не выбросят ConcurrentModificationException.
    • Пример:
      ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
      // Атомарные операции
      map.putIfAbsent("key", 1);
      map.compute("key", (k, v) -> v == null ? 1 : v + 1); // атомарное обновление
  2. CopyOnWriteArrayList<E> и CopyOnWriteArraySet<E>

    • Особенность: При каждой модификации (add, set) создаётся новая копия внутреннего массива. Дорогая запись, дешёвое чтение.
    • Использование: Идеально для сценариев, где чтение происходит на порядки чаще, чем запись (например, список слушателей events).
  3. ConcurrentLinkedQueue<E>

    • Особенность: Неблокирующая (lock-free) очередь на основе алгоритма Michael & Scott. Использует CAS.
    • Особенность итератора: Также weak consistency.
  4. BlockingQueue интерфейс и его реализации (ArrayBlockingQueue, LinkedBlockingQueue)

    • Особенность: Предоставляет блокирующие операции put() (ждёт, если очередь полна) и take() (ждёт, если очередь пуста). Основа для паттерна Producer-Consumer.

Ключевое отличие от Collections.synchronizedMap(): ConcurrentHashMap обеспечивает лучшую производительность при конкурентном чтении и записи за счёт finer-grained locking, в то время как синхронизированная версия блокирует всю коллекцию на время любой операции.

Ответ 18+ 🔞

Да ты посмотри, какие у них тут коллекции понапридумывали! Прям как в зоопарке: одни с блокировками, другие с копиями, третьи вообще без блокировок, как угорелые. Ну, давай разбираться, пока не взорвалась голова.

Вот, например, ConcurrentHashMap — это же просто песня, а не карта. Вместо того чтобы, как дурак, на всю мапу один здоровенный замок вешать, он умничка — блокирует только тот сегмент (или бакет), куда лезешь. А в новых версиях там вообще CAS-операции, атомарные такие, без прямых блокировок. Итераторы у него — weak consistency, то есть они тебе не гарантируют, что показывают самую свежую картину мира, но зато и ConcurrentModificationException в ебало не выкинут. Красота!

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Атомарные операции
map.putIfAbsent("key", 1);
map.compute("key", (k, v) -> v == null ? 1 : v + 1); // атомарное обновление

А вот CopyOnWriteArrayList и его братец CopyOnWriteArraySet — это вообще отдельная история, ёпта. Эти товарищи при любой попытке что-то изменить делают полную копию внутреннего массива. Представляешь? Каждая запись — новый массив! Запись — дорогая, как чёрт знает что, зато чтение — просто космос, потому что читать можно хоть из ста потоков одновременно, без всяких блокировок. Идеально, когда слушателей событий (listeners) на десять тысяч, а добавляют их раз в полгода.

Дальше — ConcurrentLinkedQueue. Это уже неблокирующая очередь, lock-free, на алгоритмах умных дядек Майкла и Скотта. Всё на CAS, всё летает. Итератор у неё тоже weak consistency, так что не удивляйся, если пройдёшься по очереди, а там уже половины элементов нет — они просто уже вышли из игры.

Ну и коронный номер — BlockingQueue с её реализациями (ArrayBlockingQueue, LinkedBlockingQueue). Вот это, блядь, мощь! Классический паттерн Producer-Consumer в чистом виде. Методы put() и take() — блокирующие. Очередь полная? put() будет сидеть и ждать, пока место не освободится. Очередь пустая? take() будет дрыхнуть, пока кто-нибудь что-нибудь не положит. Никакой самодельной синхронизации с wait() и notify() — всё уже сделано, бери и пользуйся.

И главное, запомни разницу, а то опять накосячишь:
ConcurrentHashMap и Collections.synchronizedMap() — это как небо и земля. Синхронизированная версия — это тупой здоровенный замок на всю коллекцию: один зашёл — все остальные ждут. А ConcurrentHashMap — хитрая жопа, он пускает всех одновременно, но так, чтобы они друг другу по головам не бегали. Производительность в многопоточке — просто овердохуищная по сравнению с синхронизированным аналогом.

Вот так-то, дружок. Выбирай инструмент по задаче, а не тыкай во всё подряд synchronized — а то ещё накосячишь, и потом дебажить эту многопоточную пургу будешь до второго пришествия.