Питання 7 · Розділ 4

Що таке Vector і чим він відрізняється від ArrayList?

Lock Coarsening — оптимізація JVM, яка об'єднує кілька послідовних synchronized-блоків в один, щоб знизити накладні витрати на блокування.

Мовні версії: English Russian Ukrainian

🟢 Junior Level

Vector — застаріла версія ArrayList, яка була в Java з самого початку.

Головна різниця:

  Vector ArrayList
Синхронізація ✅ Всі методи синхронізовані ❌ Немає
Швидкість Повільніше Швидше
Ресайз × 2 × 1.5
Коли використовувати Тільки в legacy-коді, новий код — ArrayList або CopyOnWriteArrayList  

Приклад:

// ❌ Не використовуйте Vector
Vector<String> v = new Vector<>();  // Повільний!

// ✅ Використовуйте ArrayList
List<String> list = new ArrayList<>();

Чому Vector повільний:

  • Кожен метод = synchronized
  • Навіть якщо працюєте в одному потоці!

🟡 Middle Level

Технічні відмінності

// Vector:
public synchronized boolean add(E e) {  // ← synchronized!
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

// ArrayList:
public boolean add(E e) {  // ← Немає synchronized!
    modCount++;
    add(e, elementData, size);
    return true;
}

Ресайз

Vector: capacity × 2
  10 → 20 → 40 → 80
  → Швидко росте, більше wasted memory

ArrayList: capacity × 1.5
  10 → 15 → 22 → 33
  → Економніший, менше фрагментація

Чому синхронізація Vector НЕ допомагає

// ❌ Vector НЕ потокобезпечний для складених операцій!
if (!vector.contains(obj)) {  // Перевірка
    vector.add(obj);           // ← Інший потік може втрутитися!
}

Це називається race condition **«check-then-act»**: перевірка (`contains`) та дія (`add`)  дві окремі операції, між якими інший потік може втрутитися.

// Все одно потрібен зовнішній synchronized:
synchronized (vector) {
    if (!vector.contains(obj)) {
        vector.add(obj);
    }
}

Альтернативи

// Без синхронізації:
ArrayList<String> list = new ArrayList<>();

// З синхронізацією (багато читань):
List<String> list = new CopyOnWriteArrayList<>();

// З синхронізацією (універсальний):
List<String> list = Collections.synchronizedList(new ArrayList<>());

// Для черг:
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();

Як обрати:

  • Один потік → ArrayList
  • Багато потоків, багато читань → CopyOnWriteArrayList
  • Багато потоків, змішане навантаження → Collections.synchronizedList
  • Багато потоків, черга → ConcurrentLinkedQueue

🔴 Senior Level

Lock Contention

100 потоків викликають vector.get(0):
  → Всі чекають один монітор
  → Серіалізація → 0 паралелізму!

ArrayList:
  → 100 потоків читають одночасно
  → Lock-free reading

JIT Lock Coarsening

Lock Coarsening — оптимізація JVM, яка об’єднує кілька послідовних synchronized-блоків в один, щоб знизити накладні витрати на блокування.

JVM намагається оптимізувати:
  vector.get(0);
  vector.get(1);
  vector.get(2);

→ Lock Coarsening: один lock на всі три
→ Але це маскує проблему, не вирішує!

LSP Violation: Stack наслідується від Vector

LSP (Liskov Substitution Principle) — принцип ООП: підклас повинен бути замінним базовим класом без зміни коректності програми. Stack порушує це, бо додає методи, несумісні з семантикою стека.

// Stack extends Vector!
Stack<String> stack = new Stack<>();
stack.push("A");
stack.add(0, "B");  // ← Порушує семантику стека!

// → Stack може робити все, що Vector
// → Це порушення Liskov Substitution Principle

Enumeration vs Iterator

// Vector має Enumeration (застарілий)
Enumeration<String> e = vector.elements();
while (e.hasMoreElements()) {
    e.nextElement();  // Не fail-fast!
}

// Iterator (сучасний)
Iterator<String> it = vector.iterator();
// → Fail-fast, як у ArrayList

Production Experience

Реальний сценарій: Vector убив throughput

  • Додаток: 1000 RPS, Vector для логів
  • Lock contention: 80% часу на synchronized
  • Рішення: ArrayList
  • Результат: +400% throughput

Best Practices

  1. НЕ використовуйте Vector — це legacy
  2. ArrayList — за замовчуванням
  3. CopyOnWriteArrayList — для thread-safe
  4. Collections.synchronizedList — універсальний
  5. ConcurrentLinkedQueue — для черг
  6. Stack → ArrayDeque — для стеків
  7. Enumeration → Iterator — сучасний

Резюме для Senior

  • Vector = legacy, synchronized, повільний
  • ArrayList = швидший, без синхронізації
  • Синхронізація Vector не дає реальної thread-safety
  • Lock Contention убиває паралелізм
  • Ресайз × 2 → більше фрагментація
  • Stack extends Vector = LSP violation
  • CopyOnWriteArrayList = правильна thread-safe альтернатива
  • Ніколи не використовуйте Vector у новому коді

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • Vector = legacy, всі методи synchronized → повільний навіть в одному потоці
  • Ресайз: Vector × 2 (більше фрагментація) vs ArrayList × 1.5 (економніший)
  • Синхронізація Vector НЕ дає реальної thread-safety для складених операцій (race condition «check-then-act»)
  • Lock Contention: 100 потоків викликають vector.get() → серіалізація → 0 паралелізму
  • Stack extends Vector = LSP violation — можна викликати get(index), порушуючи LIFO-семантику
  • Enumeration (Vector) застарів → використовуйте Iterator (fail-fast)
  • Альтернативи: ArrayList (один потік), CopyOnWriteArrayList (багато читань), Collections.synchronizedList (універсальний)
  • Реальний кейс: заміна Vector → ArrayList дала +400% throughput

Часті уточнюючі запитання:

  • Чому synchronized у Vector не гарантує thread-safety? — Складені операції (check-then-act) все одно вимагають зовнішнього synchronized
  • Що таке LSP violation у контексті Stack? — Stack наслідується від Vector, можна викликати add(0,e) — порушує LIFO
  • Коли JIT Lock Coarsening маскує проблему? — Об’єднує послідовні synchronized-блоки, але це не вирішує lock contention
  • Чим CopyOnWriteArrayList кращий за Vector? — Копія при запису, lock-free читання, кращий для сценаріїв «багато читань, мало записів»

Червоні прапорці (НЕ говорити):

  • ❌ “Vector — потокобезпечна заміна ArrayList” — synchronized не дає реальної thread-safety для складених операцій
  • ❌ “Vector × 2 ресайз кращий ніж ArrayList × 1.5” — навпаки, більше фрагментація та wasted memory
  • ❌ “Використовуйте Stack як стек” — Stack extends Vector, використовуйте ArrayDeque
  • ❌ “Enumeration кращий за Iterator” — Enumeration застарів, не fail-fast

Пов’язані теми:

  • [[Що таке Stack]]
  • [[В чому різниця між ArrayList та LinkedList]]
  • [[Які основні інтерфейси Collection Framework]]
  • [[Що таке Deque]]