JavaRush /Курси /JAVA 25 SELF /Вступ до багатопотоковості: навіщо вона потрібна

Вступ до багатопотоковості: навіщо вона потрібна

JAVA 25 SELF
Рівень 51 , Лекція 0
Відкрита

1. Що таке потік виконання (thread)

Потік як окрема лінія виконання

У Java (та в програмуванні загалом) потік виконання — це незалежна послідовність команд, яка йде паралельно з іншими потоками всередині однієї програми. Уявіть фабрику: у кожної швачки — свій робочий стіл і завдання; вона працює незалежно, але разом вони дають спільний результат.

За замовчуванням Java‑програма запускається з одного потоку — того, що запускає метод main. Але ніщо не заважає нам створювати додаткові потоки, щоб різні частини програми виконувалися одночасно.

Процеси й потоки: у чому різниця?

  • Процес — це «важковагова» одиниція виконання. У кожного процесу своя область пам’яті, свої змінні, свої ресурси. Процеси повністю ізольовані один від одного — якщо один «зламається», інші не постраждають.
  • Потік (thread) — це «легковагова» одиниця виконання всередині процесу. Усі потоки одного процесу спільно використовують пам’ять і ресурси. Це означає, що вони можуть легко обмінюватися даними (і, на жаль, так само легко заважати один одному).

Аналогія:
Процес — це окрема квартира: у кожної свої стіни та мешканці.
Потоки — це мешканці всередині однієї квартири: у кожного свої справи, але кухня й ванна спільні.

Як це виглядає в Java?

Коли ви запускаєте програму, JVM створює щонайменше один потік — головний (main). Але ви можете створювати нові потоки, щоб виконувати завдання паралельно.

2. Навіщо потрібна багатопотоковість

Реактивність: UI не повинен «підвисати»

Припустімо, ви пишете графічну програму — наприклад, текстовий редактор. Користувач натиснув кнопку «Зберегти», а ви пішли довго й нудно записувати файл на диск. Якщо все це робити в головному потоці, вікно програми «зависне»: користувач не зможе нічого натиснути, курсор не рухається, інтерфейс не реагує. Якщо ж зберігати файл в окремому потоці — інтерфейс залишиться чутливим, і користувач навіть зможе передумати та закрити програму.

Життєвий приклад:
Ви відкрили браузер і почали завантажувати великий файл. Якби браузер не використовував потоки, ви б не змогли ні відкрити нову вкладку, ні прокрутити сторінку, поки файл не завантажиться!

Паралельна обробка даних

Припустімо, у вас є список із тисячі файлів, які потрібно обробити (наприклад, перерахувати хеші або замінити текст). Чому б не обробити їх паралельно? Кожен потік бере свій файл і працює з ним незалежно, тож вся робота завершується в кілька разів швидше.

Приклад:
Сервер обробляє запити від сотень клієнтів. Якби сервер робив це в одному потоці, решта клієнтів чекали б своєї черги вічно. А з потоками кожен запит обробляється незалежно!

Використання багатоядерних процесорів

Сучасні процесори — це не один «мозок», а ціла команда (ядра), які можуть працювати паралельно. Якщо ваша програма використовує лише один потік, інші ядра нудьгують і грають у сапера. Якщо ж ви запускаєте кілька потоків — усі ядра зайняті справою, а програма виконується швидше.

Цікавий факт:
Навіть ваш телефон має кілька ядер, а ноутбуки й сервери — десятки! Не використовувати їх усі — все одно що купити автобус і їздити на ньому наодинці.

3. Приклади з життя

Сфера Приклад багатопотоковості
Завантаження файлів Завантаження кількох файлів одночасно
Користувацький UI Застосунок не «зависає» під час завантаження/збереження даних
Сервери Обробка багатьох мережевих запитів паралельно
Ігри Окремі потоки для фізики, графіки, музики, AI
Месенджери Отримання повідомлень, надсилання файлів, оновлення інтерфейсу
Обробка відео Обробка кадрів паралельно

Міні-аналогія:
Кухар готує суп, а паралельно духовка пече пиріг, а робот-пилосос прибирає підлогу — усе це відбувається одночасно, і вечеря готується швидше!

4. Потенційні складнощі багатопотоковості

Стан гонки (race condition)
Коли кілька потоків одночасно змінюють одну й ту саму змінну, результат може бути непередбачуваним. Наприклад, якщо два потоки одночасно збільшують спільний лічильник, підсумкове значення може бути некоректним. Про це докладніше поговоримо в одній із наступних лекцій.

Синхронізація
Щоб потоки не заважали один одному, доводиться вигадувати способи «домовитися» — хто й коли може змінювати дані. Це називається синхронізація. Для цього є спеціальні ключові слова й конструкції (synchronized, блокування тощо), про які ми поговоримо пізніше.

Deadlock (взаємне блокування)
Іноді потоки можуть так «подружитися», що чекатимуть один одного вічно, і програма зависне. Це називається deadlock — і це одна з найпідступніших помилок у багатопотоковому програмуванні.

Налагодження та тестування
Помилки в багатопотокових програмах дуже складно виловити: іноді все працює, іноді — ні. Іноді помилка проявляється лише на сервері або в користувача, а у вас на комп’ютері все ідеально. Це робить тестування та налагодження багатопотокового коду справжнім квестом для розробника.

5. Короткий огляд: як виглядає багатопотокова програма

Приклад без потоків:

public class Main {
    public static void main(String[] args) {
        // Рахуємо до 5
        for (int i = 1; i <= 5; i++) {
            System.out.println(i);
        }
        // Виводимо літери
        for (char c = 'A'; c <= 'E'; c++) {
            System.out.println(c);
        }
    }
}

Виведення завжди однакове:

1
2
3
4
5
A
B
C
D
E

Приклад із потоками:

public class Main {
    public static void main(String[] args) {
        Thread numbers = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println(i);
                try {
                    Thread.sleep(100); // Трохи зачекаємо
                } catch (InterruptedException e) {
                    // Ігноруємо
                }
            }
        });

        Thread letters = new Thread(() -> {
            for (char c = 'A'; c <= 'E'; c++) {
                System.out.println(c);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // Ігноруємо
                }
            }
        });

        numbers.start();
        letters.start();
    }
}

Виведення буде перемішаним:

1
A
2
B
3
C
4
D
5
E

або, якщо потоки «змагаються», порядок може бути іншим. Головне — обидва цикли виконуються паралельно!

6. Корисні нюанси

Візуальна схема: як потоки працюють разом

+-------------------+     +-------------------+
|   Головний потік  |     |    Другий потік   |
+-------------------+     +-------------------+
| 1 | 2 | 3 | 4 | 5 |     | A | B | C | D | E |
+-------------------+     +-------------------+
        |                         |
        |     Обидва працюють     |
        |      одночасно          |
        +-------------------------+

Де Java використовує потоки «під капотом»

  • Збирання сміття (Garbage Collector) — окремий потік очищає невикористовувані об’єкти.
  • Потоки введення-виведення (IO) — читання й запис файлів, мережеві з’єднання.
  • Сервери та веб-застосунки — кожен клієнтський запит обробляється в окремому потоці.
  • Таймери, планувальники завдань — виконання завдань за розкладом.

7. Типові помилки новачків

Помилка № 1: Очікування, що потоки завжди прискорюють програму.
Насправді, якщо у вас однопроцесорна машина або ви неправильно організували роботу, багатопотоковість може лише сповільнити виконання через «плутанину» та накладні витрати на перемикання між потоками.

Помилка № 2: Ігнорування проблем синхронізації.
Багато хто впевнений: «Я ж просто запускаю два потоки, що тут може піти не так?» Але якщо обидва потоки змінюють одну змінну, результат може бути цілком неочікуваним.

Помилка № 3: Використання потоків для всього підряд.
Не варто запускати окремий потік для кожної дрібниці. Потоки — це ресурс, і їхня надмірна кількість може призвести до значних уповільнень і навіть до збою програми.

Помилка № 4: Відсутність обробки помилок.
Потоки можуть викидати винятки (наприклад, під час роботи з файлами або мережею). Якщо не обробляти ці помилки, програма може аварійно завершитися або «зависнути».

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ