Очереди задач в Java: полное руководство для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: зачем нужны очереди задач в 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()

Пример: Producer-Cons

Блоги

Рекомендаций по книгам