Ответ
Concurrent Collections из пакета java.util.concurrent предоставляют потокобезопасные реализации стандартных интерфейсов коллекций, оптимизированные для многопоточного доступа.
Основные классы и их применение:
-
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); // атомарное обновление
-
CopyOnWriteArrayList<E>иCopyOnWriteArraySet<E>- Особенность: При каждой модификации (
add,set) создаётся новая копия внутреннего массива. Дорогая запись, дешёвое чтение. - Использование: Идеально для сценариев, где чтение происходит на порядки чаще, чем запись (например, список слушателей events).
- Особенность: При каждой модификации (
-
ConcurrentLinkedQueue<E>- Особенность: Неблокирующая (lock-free) очередь на основе алгоритма Michael & Scott. Использует CAS.
- Особенность итератора: Также weak consistency.
-
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 — а то ещё накосячишь, и потом дебажить эту многопоточную пургу будешь до второго пришествия.