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

Ответ

Работал с коллекциями из пакета java.util.concurrent, каждая из которых решает конкретные проблемы параллелизма.

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

Коллекция Принцип работы Лучший сценарий использования
ConcurrentHashMap Сегментирование (bucket-level locking) или CAS для Java 8+. Высококонкурентные put/get операции. Кэши, shared-словари.
CopyOnWriteArrayList / CopyOnWriteArraySet Создание новой копии массива при каждой модификации. Частое чтение, редкая запись (Listener-списки, конфигурации).
ConcurrentLinkedQueue Неблокирующая очередь на основе CAS (Compare-And-Swap). Высокопроизводительная очередь без ограничения размера.
BlockingQueue (напр., ArrayBlockingQueue, LinkedBlockingQueue) Блокирует поток при попытке взять из пустой или положить в полную очередь. Паттерн Producer-Consumer, пулы соединений.
ConcurrentSkipListMap / ConcurrentSkipListSet Потокобезопасная реализация на основе skip-list. Когда нужна отсортированная потокобезопасная коллекция.

Пример Producer-Consumer с BlockingQueue:

BlockingQueue<Message> queue = new LinkedBlockingQueue<>(100);

// Producer поток
public void run() {
    queue.put(new Message("data")); // Блокируется, если очередь полна
}

// Consumer поток
public void run() {
    Message msg = queue.take(); // Блокируется, если очередь пуста
    process(msg);
}

Важные замечания:

  • Итераторы ConcurrentHashMap и ConcurrentLinkedQueue являются слабо согласованными (weakly consistent) и не бросают ConcurrentModificationException.
  • Атомарные составные операции (например, putIfAbsent, computeIfAbsent) в ConcurrentHashMap безопасны и часто более эффективны, чем внешняя синхронизация.
  • CopyOnWriteArrayList может быть дорогим по памяти при частых записях, но обеспечивает полную безопасность итераторов.

Ответ 18+ 🔞

Да ты посмотри, какие у нас инструменты для параллельного пиздеца в java.util.concurrent! Каждая штука — как спецназовец, для своей конкретной задачи. Сейчас разберём, кто чем дышит.

Что есть и для чего:

Коллекция Как работает, блядь Где её впендюрить, чтобы не обосраться
ConcurrentHashMap Раньше сегменты делила, теперь в Java 8+ умная — CAS'ами и прочей чёрной магией оперирует. Когда все потоки дружно долбят в один словарь put/get. Идеально для кэшей, общих справочников — просто овердохуища запросов в секунду.
CopyOnWriteArrayList / CopyOnWriteArraySet Прикинь, сука, каждый раз, когда пишешь в список, он создаёт полную новую копию массива! Читают все постоянно, а пишут раз в год. Списки слушателей, конфиги — вот их родная стихия. Если часто писать — память сожрёт, как не в себя.
ConcurrentLinkedQueue Неблокирующая очередь, вся на CAS'ах. Никого не ждёт, работает как швейцарские часы. Нужна быстрая, неограниченная очередь, где производитель и потребитель не спят, а пашут.
BlockingQueue (типа ArrayBlockingQueue, LinkedBlockingQueue) А вот это уже серьёзные пацаны. Пустая очередь? Потребитель уснёт. Полная? Производитель встанет в позу. Всё честно. Классика жанра — Producer-Consumer. Пул соединений, задачи между потоками гонять. Работает, как часы, ебать.
ConcurrentSkipListMap / ConcurrentSkipListSet Потокобезопасная реализация на основе этой вашей skip-list. Сложная, но сортированная. Когда нужна не просто мапа, а чтобы ещё и порядок был, и чтобы потоки не перегрызли друг другу глотки.

Вот, смотри, как Producer-Consumer на BlockingQueue выглядит, красота:

BlockingQueue<Message> queue = new LinkedBlockingQueue<>(100);

// Поток-поставщик (Producer)
public void run() {
    queue.put(new Message("data")); // Встанет и будет бздеть, если очередь забита под завязку
}

// Поток-потребитель (Consumer)
public void run() {
    Message msg = queue.take(); // А этот уснёт, как сурок, если очередь пуста
    process(msg);
}

А теперь, блядь, важные детали, чтобы не сесть в лужу:

  • Итераторы у ConcurrentHashMap и ConcurrentLinkedQueue — слабо согласованные. Они тебе не выкинут ConcurrentModificationException, но могут и не показать свежие изменения, которые только что сделал другой поток. Не ожидай от них полной актуальности, хитрая жопа.
  • Сложные операции в ConcurrentHashMap, типа putIfAbsent или computeIfAbsent — атомарные и ебейшие быстрые. Не городи велосипед со своей синхронизацией, используй их — они для того и придуманы.
  • CopyOnWriteArrayList — это пиздец какой удобный, когда пишешь редко. Итераторы у него абсолютно безопасные. Но если начать в него активно писать, он память сожрёт, как мартышлюшка бананы. Помни об этом, ёпта.