Введение: зачем нужны очереди задач в Java?
Каждый разработчик рано или поздно сталкивается с необходимостью организовать потоковую обработку данных. Представьте веб-сервер, который принимает тысячи запросов в секунду, или систему, обрабатывающую заказы интернет-магазина. Обрабатывать всё сразу невозможно — ресурсы ограничены. Здесь на помощь приходят очереди задач (task queues).
В Java очередь задач — это не просто структура данных FIFO (First In, First Out). Это мощный инструмент для:
- Разделения производства и потребления задач (шаблон Producer-Consumer)
- Управления нагрузкой — задачи накапливаются в очереди, когда система занята
- Асинхронной обработки — поток-производитель не ждёт, пока задача выполнится
- Балансировки между потоками — несколько воркеров забирают задачи из общей очереди
В этой статье мы разберём все основные аспекты работы с очередями в Java: от базовых интерфейсов Collection Framework до потокобезопасных очередей для многопоточных приложений.
Базовые очереди: Queue и Deque
Интерфейс Queue
Интерфейс java.util.Queue — это основа всех очередей в Java. Он предоставляет стандартные операции для работы с очередью:
| Операция | Исключение при ошибке | Возврат специального значения |
|---|---|---|
| Добавление | add(e) | offer(e) |
| Извлечение и удаление | remove() | poll() |
| Просмотр (без удаления) | element() | peek() |
Разница между методами очевидна: если очередь пуста, remove() и element() выбросят NoSuchElementException, а poll() и peek() вернут null.
Самые популярные реализации Queue:
- LinkedList — двусвязный список, работает как обычная FIFO очередь
- PriorityQueue — очередь с приоритетом, элементы извлекаются по порядку, заданному Comparator
- ArrayDeque — двухсторонняя очередь на основе массива
Пример: простая очередь задач на LinkedList
import java.util.LinkedList;
import java.util.Queue;
public class SimpleTaskQueue {
public static void main(String[] args) {
Queue<String> taskQueue = new LinkedList<>();
// Добавляем задачи
taskQueue.offer("Загрузить данные из БД");
taskQueue.offer("Отправить email-уведомление");
taskQueue.offer("Сгенерировать отчёт");
System.out.println("Очередь: " + taskQueue);
// Обрабатываем все задачи
while (!taskQueue.isEmpty()) {
String task = taskQueue.poll();
System.out.println("Выполняю: " + task);
}
System.out.println("Все задачи выполнены!");
}
}
Интерфейс Deque
Deque (Double Ended Queue) позволяет добавлять и извлекать элементы с обоих концов. Это даёт гибкость: очередь может работать как стек (LIFO) или как обычная очередь (FIFO).
Основные методы Deque:
addFirst()/offerFirst()— добавить в началоaddLast()/offerLast()— добавить в конецremoveFirst()/pollFirst()— извлечь с началаremoveLast()/pollLast()— извлечь с конца
Пример: Deque как стек для отложенных задач
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeExample {
public static void main(String[] args) {
Deque<String> taskStack = new ArrayDeque<>();
// Добавляем задачи с приоритетом (новые — важнее)
taskStack.push("Критический баг: исправить срочно");
taskStack.push("Оптимизация запроса к БД");
taskStack.push("Рефакторинг старого кода");
// Извлекаем в порядке LIFO (как стек)
while (!taskStack.isEmpty()) {
String task = taskStack.pop();
System.out.println("Срочная задача: " + task);
}
}
}
Потокобезопасные очереди: BlockingQueue
В многопоточных приложениях обычные очереди не подходят — они не синхронизированы. Если два потока одновременно вызовут add() и poll(), данные могут быть повреждены. Для таких случаев Java предоставляет интерфейс BlockingQueue из пакета java.util.concurrent.
Ключевая особенность BlockingQueue — блокирующие операции:
put(e)— ждёт, пока в очереди появится место (для очередей с ограниченной ёмкостью)take()— ждёт, пока в очереди появится элемент
Популярные реализации BlockingQueue
| Класс | Описание | Особенности |
|---|---|---|
| ArrayBlockingQueue | Ограниченная очередь на массиве | Фиксированный размер, справедливая блокировка (опционально) |
| LinkedBlockingQueue | Очередь на связанном списке | Может быть ограниченной и неограниченной |
| PriorityBlockingQueue | Приоритетная очередь | Неограниченная, элементы сортируются |
| DelayQueue | Очередь с задержкой | Элементы доступны только после истечения задержки |
| SynchronousQueue | Очередь без ёмкости | Каждая операция put() ждёт соответствующую take() |