Питання 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]]