Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Потоки выполнения

Поток выполнения (thread) — это отдельная последовательность команд внутри программы. Обычно программа выполняет код строка за строкой в одном потоке — главном, он называется main. Если запустить несколько потоков, они работают одновременно: пока один ждёт ответа от сети, другой считает данные. Не путайте их с потоками данных (Stream API) — это разные понятия.

Первый поток

Поток создают классом Thread, передав ему задачу — лямбду без параметров. Метод start() запускает поток, а join() заставляет текущий поток дождаться его завершения:

void main() throws InterruptedException {
    Thread worker = new Thread(() -> IO.println("Привет из другого потока"));
 
    IO.println("Запускаем");
    worker.start();
    worker.join();
    IO.println("Готово");
}

Результат:

Запускаем
Привет из другого потока
Готово

Благодаря join() строка «Готово» гарантированно выводится последней. join и другие методы ожидания могут выбросить проверяемое InterruptedException — если поток попросят прервать ожидание, — поэтому у main стоит throws.

Порядок не гарантирован

Потоки работают независимо, и операционная система сама решает, какому из них дать процессор. Поэтому порядок вывода двух потоков меняется от запуска к запуску:

void count(String name) {
    for (int i = 1; i <= 3; i++) {
        IO.println(name + i);
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            return;
        }
    }
}
 
void main() throws InterruptedException {
    Thread a = new Thread(() -> count("A"));
    Thread b = new Thread(() -> count("B"));
 
    a.start();
    b.start();
    a.join();
    b.join();
}

Результат (один из возможных — у вас порядок может быть другим):

A1
B1
B2
A2
B3
A3

Внутри одного потока порядок сохраняется: A1 всегда раньше A2. А вот как строки A и B перемешаются, предсказать нельзя.

Паузы

Thread.sleep приостанавливает текущий поток на заданное время. Это удобно, чтобы имитировать долгую работу — запрос к серверу или чтение большого файла:

void main() throws InterruptedException {
    IO.println("Загружаю...");
    Thread.sleep(Duration.ofMillis(500));
    IO.println("Загружено");
}

Результат:

Загружаю...
Загружено

Имена потоков

У каждого потока есть имя. Его возвращает Thread.currentThread().getName() — так можно узнать, в каком потоке выполняется код. Задать имя удобно через построитель Thread.ofPlatform():

void main() throws InterruptedException {
    Thread worker = Thread.ofPlatform()
            .name("загрузчик")
            .start(() -> IO.println("Работает: " + Thread.currentThread().getName()));
 
    worker.join();
    IO.println("Работает: " + Thread.currentThread().getName());
}

Результат:

Работает: загрузчик
Работает: main

Метод start построителя сразу и создаёт, и запускает поток.

Результат из потока

Лямбда в Thread ничего не возвращает. Простой способ собрать результаты — дать каждому потоку свою ячейку массива и прочитать массив после join:

void main() throws InterruptedException {
    int[] sums = new int[2];
 
    Thread first = new Thread(() -> sums[0] = IntStream.rangeClosed(1, 500).sum());
    Thread second = new Thread(() -> sums[1] = IntStream.rangeClosed(501, 1000).sum());
    first.start();
    second.start();
    first.join();
    second.join();
 
    IO.println("Сумма от 1 до 1000: " + (sums[0] + sums[1]));
}

Результат:

Сумма от 1 до 1000: 500500

Потоки пишут в разные ячейки и не мешают друг другу, а join гарантирует, что к моменту чтения оба закончили работу. Удобнее получать результаты через пулы потоков — о них следующий урок.

Частая ошибка

Если вызвать run() вместо start(), новый поток не создаётся: задача просто выполнится в текущем потоке, как обычный метод:

void main() {
    Thread worker = new Thread(() -> IO.println("Выполняюсь в " + Thread.currentThread().getName()));
 
    worker.run();
}

Результат:

Выполняюсь в main

Ошибки нет, но и параллельности тоже. Чтобы код выполнился в отдельном потоке, всегда вызывайте start().

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.