Вопрос 9 · Раздел 4

Что такое Queue и какие реализации существуют?

4. ConcurrentLinkedQueue — lock-free 5. BlockingQueue — Producer-Consumer 6. null запрещён в большинстве очередей 7. SynchronousQueue — для прямой передачи

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Queue (Очередь) — структура данных по принципу FIFO (First In, First Out).

Простая аналогия: Очередь в магазине. Кто первый пришёл, тот первый обслужен.

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

Действие Исключение Безопасный метод
Добавить add(e) offer(e)
Забрать remove() poll()
Посмотреть element() peek()
Queue<String> queue = new ArrayDeque<>();

queue.offer("A");  // Добавили
queue.offer("B");

String first = queue.poll();  // → "A" (первый ушёл)
String next = queue.peek();   // → "B" (посмотрели)

offer/poll/peek предпочтительнее в production-коде, потому что они не бросают исключения — возвращают false/null при ошибке, что позволяет обработать ситуацию gracefully.


🟡 Middle Level

Основные реализации

ArrayDeque:

// Кольцевой массив → O(1) вставка/удаление
Queue<String> queue = new ArrayDeque<>();
queue.offer("A");  // Быстро
queue.poll();      // Быстро

PriorityQueue:

// Binary Heap → извлечение по приоритету
Queue<Integer> pq = new PriorityQueue<>();
pq.offer(5);
pq.offer(1);
pq.offer(3);
pq.poll();  // → 1 (наименьший!)

Методы: Exception vs Special Value

// Бросают exception:
queue.add(e);     // IllegalStateException если full
queue.remove();   // NoSuchElementException если пуста
queue.element();  // NoSuchElementException если пуста

// Возвращают special value:
queue.offer(e);   // false если full
queue.poll();     // null если пуста
queue.peek();     // null если пуста

// ✅ В production используйте offer/poll/peek!

Thread-safe очереди

// Lock-free (высокая интенсивность)
Queue<String> q = new ConcurrentLinkedQueue<>();

// Blocking (Producer-Consumer)
BlockingQueue<String> q = new LinkedBlockingQueue<>();
q.put("A");       // Блокирует если full
String e = q.take();  // Блокирует если empty

// Bounded (контроль памяти)
BlockingQueue<String> q = new ArrayBlockingQueue<>(100);
// → Не больше 100 элементов → защита от OOM

🔴 Senior Level

Когда НЕ использовать Queue

  1. Если нужен доступ к произвольному элементу — возьмите List
  2. Если важна уникальность — возьмите Set
  3. Если данные нужно сортировать — PriorityQueue с компаратором или TreeSet

Почему очереди не любят null?

// Большинство очередей запрещают null
queue.offer(null);  // → NullPointerException!

// Почему?
// poll() и peek() возвращают null для пустой очереди
// Если null валидный элемент → двусмысленность!
// → "очередь пуста" или "в голове null"?

BlockingQueue паттерны

// Producer-Consumer
BlockingQueue<Task> queue = new LinkedBlockingQueue<>(1000);

// Producer
queue.put(task);  // Блокирует если full → backpressure!

**Backpressure** (обратное давление)  механизм, при котором медленный потребитель сигнализирует быстрому производителю замедлиться. Bounded Queue блокирует producer, пока consumer не освободит место.

// Consumer
Task task = queue.take();  // Блокирует если empty

SynchronousQueue

// Очередь с нулевой ёмкостью!
BlockingQueue<String> q = new SynchronousQueue<>();

// put блокирует, пока кто-то не сделает take
// Блокировка = поток «засыпает» и не расходует CPU, пока другой поток не заберёт элемент. Это эффективно для синхронизации без активного ожидания (busy-wait).
q.put("A");  // Ждёт...

// В другом потоке:
String s = q.take();  // → "A", put разблокируется

// Использование: Executors.newCachedThreadPool()

DelayQueue

// Элементы доступны только после задержки
class DelayedTask implements Delayed {
    private final long deadline;
    
    public long getDelay(TimeUnit unit) {
        return unit.convert(deadline - System.currentTimeMillis(), MILLISECONDS);
    }
}

DelayQueue<DelayedTask> queue = new DelayQueue<>();
// poll() вернёт элемент только когда getDelay() <= 0

TransferQueue (Java 7+)

// Producer ждёт, пока Consumer заберёт элемент
TransferQueue<String> q = new LinkedTransferQueue<>();

// Producer
q.transfer("message");  // Блокирует пока consumer не заберёт!

// Consumer
String msg = q.take();  // Забирает, producer разблокируется

// Использование: синхронная передача данных
// Синхронная передача = producer ждёт конкретного момента, когда consumer *примет* элемент (а не просто положит в буфер). Аналог: передача файла из рук в руки, а не оставление в почтовом ящике.

Production Experience

Реальный сценарий: Bounded Queue спасла от OOM

  • Producer быстрее Consumer → очередь росла
  • Unbounded Queue → OOM через 2 часа
  • Решение: ArrayBlockingQueue(10000)
  • Результат: Producer блокируется → стабильная система

Best Practices

  1. offer/poll/peek вместо add/remove/element
  2. ArrayDeque — для single-thread
  3. Bounded Queue — для backpressure
  4. ConcurrentLinkedQueue — lock-free
  5. BlockingQueue — Producer-Consumer
  6. null запрещён в большинстве очередей
  7. SynchronousQueue — для прямой передачи

Резюме для Senior

  • Queue = FIFO, offer/poll/peek предпочтительнее
  • ArrayDeque — fastest single-thread
  • BlockingQueue — Producer-Consumer с backpressure
  • Bounded — защита от OOM
  • ConcurrentLinkedQueue — lock-free
  • TransferQueue — синхронная передача
  • null запрещён (двусмысленность с poll)

🎯 Шпаргалка для интервью

Обязательно знать:

  • Queue = FIFO (First In, First Out), методы: offer/poll/peek предпочтительнее add/remove/element
  • offer/poll/peek возвращают false/null вместо exception — безопаснее для production
  • ArrayDeque — fastest single-thread (кольцевой массив, O(1) вставка/удаление)
  • PriorityQueue = Binary Heap, извлечение по приоритету (наименьший элемент), O(log n)
  • BlockingQueue = Producer-Consumer паттерн, put/take блокируют, bounded queue = backpressure
  • SynchronousQueue = нулевая ёмкость, producer ждёт пока consumer заберёт элемент
  • TransferQueue (Java 7+) — синхронная передача: transfer() блокирует пока consumer не заберёт
  • null запрещён в большинстве очередей — двусмысленность с poll() возвращающим null

Частые уточняющие вопросы:

  • Почему offer/poll/peek лучше add/remove/element? — Не бросают exception, позволяют обработать gracefully
  • Зачем bounded queue? — Backpressure: медленный потребитель блокирует быстрого производителя, защита от OOM
  • Чем SynchronousQueue отличается от обычной очереди? — Нулевая ёмкость, put блокирует пока кто-то не сделает take
  • Когда использовать TransferQueue? — Для синхронной передачи данных, producer ждёт конкретного момента приёма

Красные флаги (НЕ говорить):

  • ❌ “Queue позволяет хранить null” — большинство реализаций запрещают null (двусмысленность с poll)
  • ❌ “Unbounded Queue — нормальный выбор для production” — без bounded queue риск OOM от быстрого производителя
  • ❌ “add/remove/element — лучший выбор” — они бросают exception, offer/poll/peek безопаснее
  • ❌ “PriorityQueue извлекает наибольший элемент” — по умолчанию извлекает НАИМЕНЬШИЙ (нужен обратный Comparator для наибольшего)

Связанные темы:

  • [[Какие основные интерфейсы Collection Framework]]
  • [[Что такое Deque]]
  • [[В чём разница между List, Set и Queue]]
  • [[Что такое Stack]]