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 предоставляет три конструктора:
ArrayBlockingQueue(int capacity)— создаёт очередь с заданной ёмкостью и несправедливым режимом доступа.ArrayBlockingQueue(int capacity, boolean fair)— создаёт очередь с заданной ёмкостью и указанным режимом справедливости.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
| Характеристика | ArrayBlockingQueue | LinkedBlockingQueue | SynchronousQueue |
|---|---|---|---|
| Ёмкость | Фиксированная | Опционально ограниченная (по умолчанию 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 →