Открыть сервис

ArrayBlockingQueue

ArrayBlockingQueue — это класс из пакета java.util.concurrent языка программирования Java, реализующий интерфейс BlockingQueue. Он представляет собой потокобезопасную, ограниченную по размеру очередь, основанную на массиве. Элементы в очереди хранятся в порядке FIFO (первым пришёл — первым ушёл). Главной особенностью ArrayBlockingQueue является то, что её ёмкость (максимальное количество элементов) задаётся при создании и не может быть изменена впоследствии.

Принцип работы

ArrayBlockingQueue использует циклический массив для хранения элементов. Два указателя — takeIndex (индекс для извлечения) и putIndex (индекс для вставки) — перемещаются по массиву, обеспечивая эффективное использование памяти. Когда очередь пуста, операции извлечения блокируются до появления нового элемента. Когда очередь полна, операции вставки блокируются до освобождения места.

Все операции синхронизированы с помощью одного объекта ReentrantLock (блокировка повторного входа). Для координации потоков используются два условия (condition): notEmpty (сигнализирует, что очередь не пуста) и notFull (сигнализирует, что очередь не полна). Это позволяет избежать «активного ожидания» (busy waiting) и эффективно управлять потоками.

Основные характеристики

  • Ёмкость: фиксированная, задаётся в конструкторе. Не может быть изменена после создания очереди.
  • Потокобезопасность: все операции над очередью безопасны для использования из нескольких потоков без дополнительной синхронизации.
  • Порядок элементов: строгий FIFO.
  • Справедливость (fairness): конструктор позволяет указать флаг fair. При fair = true потоки, ожидающие доступа к очереди, обслуживаются в порядке поступления (FCFS — first come, first served). Это может снизить производительность, но предотвращает «голодание» потоков. По умолчанию fair = false.
  • Производительность: операции вставки и извлечения имеют амортизированную сложность O(1). Однако из-за использования блокировок ArrayBlockingQueue может быть менее производительным, чем неблокирующие очереди (например, ConcurrentLinkedQueue) в сценариях с очень высокой конкуренцией.

Конструкторы

ArrayBlockingQueue предоставляет три конструктора:

  1. ArrayBlockingQueue(int capacity) — создаёт очередь с заданной ёмкостью и несправедливым режимом доступа.
  2. ArrayBlockingQueue(int capacity, boolean fair) — создаёт очередь с заданной ёмкостью и указанным режимом справедливости.
  3. ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) — создаёт очередь с заданной ёмкостью, режимом справедливости и инициализирует её элементами из переданной коллекции. Если коллекция содержит больше элементов, чем capacity, или содержит null, выбрасывается исключение.

Основные методы

ArrayBlockingQueue реализует все методы интерфейса BlockingQueue, которые можно разделить на несколько категорий:

Методы вставки

МетодПоведение при полной очереди
add(E e)Выбрасывает IllegalStateException
offer(E e)Возвращает false
offer(E e, long timeout, TimeUnit unit)Ждёт указанное время, затем возвращает false
put(E e)Блокируется до появления свободного места

Методы извлечения

МетодПоведение при пустой очереди
remove()Выбрасывает NoSuchElementException
poll()Возвращает null
poll(long timeout, TimeUnit unit)Ждёт указанное время, затем возвращает null
take()Блокируется до появления элемента

Методы просмотра

МетодПоведение при пустой очереди
element()Выбрасывает NoSuchElementException
peek()Возвращает null

Дополнительные методы

  • int remainingCapacity() — возвращает количество свободных мест в очереди.
  • int size() — возвращает текущее количество элементов.
  • boolean isEmpty() — проверяет, пуста ли очередь.
  • boolean contains(Object o) — проверяет наличие элемента.
  • Object[] toArray() и T[] toArray(T[] a) — преобразуют очередь в массив.
  • void clear() — удаляет все элементы.
  • int drainTo(Collection<? super E> c) и int drainTo(Collection<? super E> c, int maxElements) — перемещает элементы из очереди в указанную коллекцию.

Пример использования

```java import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue;

public class ProducerConsumerExample { public static void main(String[] args) { BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);

// Поток-производитель Thread producer = new Thread(() -> { try { for (int i = 0; i < 20; i++) { queue.put(i); System.out.println("Произведено: " + i); Thread.sleep(100); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } });

// Поток-потребитель Thread consumer = new Thread(() -> { try { while (true) { Integer value = queue.take(); System.out.println("Потреблено: " + value); Thread.sleep(200); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } });

producer.start(); consumer.start(); } } ```

Применение

ArrayBlockingQueue широко используется в многопоточных приложениях Java для реализации шаблона «производитель-потребитель» (producer-consumer). Она подходит для сценариев, где:

  • Необходимо ограничить количество элементов в очереди (например, для предотвращения переполнения памяти).
  • Требуется строгая потокобезопасность без использования внешних блокировок.
  • Важен порядок обработки элементов (FIFO).

Примеры использования:

  • Пул потоков (ThreadPoolExecutor) использует ArrayBlockingQueue для хранения задач, ожидающих выполнения.
  • Системы обработки сообщений, где производители создают сообщения, а потребители их обрабатывают.
  • Ограничение нагрузки на серверные компоненты (backpressure).

Сравнение с другими BlockingQueue

ХарактеристикаArrayBlockingQueueLinkedBlockingQueueSynchronousQueue
ЁмкостьФиксированнаяОпционально ограниченная (по умолчанию Integer.MAX_VALUE)0 (нет буфера)
Структура данныхМассивСвязный списокНет
ПроизводительностьВысокая при малых размерахВысокая при больших размерахВысокая, но только для прямого обмена
Потребление памятиПредсказуемоеЗависит от количества элементовМинимальное

Ограничения и особенности

  • Нельзя хранить null: любая попытка вставить null приводит к выбросу NullPointerException.
  • Фиксированная ёмкость: если требуется динамическое изменение размера, следует использовать LinkedBlockingQueue или PriorityBlockingQueue.
  • Блокировки: при высокой конкуренции и большом количестве потоков использование одного ReentrantLock может стать узким местом. В таких случаях может быть эффективнее LinkedBlockingQueue, использующая раздельные блокировки для вставки и извлечения.
  • Итерация: итератор, возвращаемый методом iterator(), является «слабо согласованным» (weakly consistent). Он не выбрасывает ConcurrentModificationException и гарантирует обход элементов, существовавших на момент создания итератора, но может не отражать последующие изменения.

Источники

  • Java Documentation: Class ArrayBlockingQueue (Java SE 17 & JDK 17)
  • Brian Goetz, «Java Concurrency in Practice», 2006
  • Doug Lea, «Concurrent Programming in Java: Design Principles and Patterns», 1999
  • Официальная документация Oracle по пакету java.util.concurrent

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →