Skip to the content.

Технологии программирования

Назад на главную

Лекция 11. Введение в многопоточность в Java **

1. Что такое многопоточность?

Многопоточность — это способность программы выполнять несколько действий одновременно.

Почему это полезно?


2. Процессы и потоки

Прежде чем говорить о потоках, важно разделить два понятия.

2.1. Что такое процесс?

Процесс — это запущенная программа. Операционная система выделяет процессу:

Каждый процесс работает изолированно от других.

Когда вы запускаете Java-приложение (java Main), создаётся процесс JVM.


2.2. Что такое поток?

Поток (Thread) — это единица выполнения внутри процесса. Потоки:

Сколько потоков может быть в процессе?

Сколько угодно — ограничение только по ресурсам ОС.

Обычная JVM-программа создаёт десятки потоков автоматом:

Даже если вы не создаёте потоки вручную — они есть.


2.3. Как Java управляет потоками?

JVM не реализует планировщик потоков сама → она полагается на планировщик ОС.

Важно: Java-поток = системный поток операционной системы.


2.4. Простейший пример создания потока

Thread t = new Thread(() -> {
    System.out.println("Hello from thread!");
});

t.start(); // запускает новый поток
System.out.println("Main thread finished");

3. Почему многопоточность — это сложно

Многопоточность ускоряет программу, но создаёт новые, непривычные проблемы.


3.1. Проблема №1: гонки данных (race condition)

Это ситуация, когда два потока одновременно читают и изменяют общие данные, и результат становится неправильным.

Возьмём класс Wallet:

class Wallet {
    private int balance = 100;

    public void withdraw(int amount) {
        if (balance >= amount) {
            balance = balance - amount;
        }
    }

    public int getBalance() {
        return balance;
    }
}

Два потока снимают по 80:

Wallet wallet = new Wallet();

Thread t1 = new Thread(() -> wallet.withdraw(80));
Thread t2 = new Thread(() -> wallet.withdraw(80));

t1.start();
t2.start();
Что происходит внутри (пошагово):
  1. Поток 1 читает balance: 100
  2. Поток 2 читает balance: 100
  3. Поток 1 выполняет balance = 20
  4. Поток 2 выполняет balance = -60

Итог: -60 — некорректно.


3.2. Проблема №2: взаимная блокировка (deadlock)

public static void transfer(Wallet from, Wallet to, int amount) {
    synchronized (from) {
        synchronized (to) {
            from.withdraw(amount);
            to.add(amount);
        }
    }
}
Как возникает deadlock?
Wallet w1 = new Wallet(100);
Wallet w2 = new Wallet(100);

Thread t1 = new Thread(() -> Wallet.transfer(w1, w2, 10));
Thread t2 = new Thread(() -> Wallet.transfer(w2, w1, 20));

3.3. Проблема №3: проблема видимости (visibility)

class Wallet {
    private boolean active = true;

    public void deactivate() {
        active = false;
    }

    public void watch() {
        while (active) { }
        System.out.println("Wallet deactivated");
    }
}

Исправление:

private volatile boolean active = true;

4. Как Java решает проблемы многопоточности

4.1. synchronized

public synchronized void withdraw(int amount) {
    if (balance >= amount) {
        balance -= amount;
    }
}

4.2. synchronized-блоки

synchronized (this) {
    balance -= amount;
}

4.3. volatile

Используется для гарантии видимости изменений между потоками.


5. Concurrent Collections

Коллекция Для чего подходит
ConcurrentHashMap потокобезопасная Map, высокая производительность
CopyOnWriteArrayList редко изменяется, часто читается
ConcurrentLinkedQueue неблокирующая очередь

6. ExecutorService

ExecutorService exec = Executors.newFixedThreadPool(4);

exec.submit(() -> {
    System.out.println("Task executed!");
});

exec.shutdown();

7. Что нужно запомнить

✔ Потоки выполняются параллельно и разделяют общую память
✔ Основные проблемы: гонки данных, deadlock, visibility
✔ Инструменты: synchronized, volatile, concurrent collections, ExecutorService