Перейти к содержимому
Теория · Потоки: первое знакомство · урок 1 из 6

Процесс и поток

Чему вы научитесь

Чему вы научитесь:

  • отличать процесс от потока и называть, что потоки одной программы делят между собой;
  • запускать поток через start(), дожидаться его через join() и не путать start() с run();
  • объяснять, почему count++ из двух потоков теряет увеличения;
  • защищать общий счётчик с помощью synchronized и AtomicInteger.

Представьте кухню ресторана. На ней работают несколько поваров: один варит суп, другой режет салат, третий жарит котлеты. Работа идёт одновременно, и никто не ждёт, пока другой закончит своё блюдо.

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

Зачем это программисту? Почти любая серьёзная программа делает несколько дел сразу. Сервер отвечает тысячам пользователей, игра рисует кадр и в то же время загружает уровень. В Java для этого есть потоки, и в этой теме кухня — программа, а повара — её потоки.

Что значит «одновременно»

Команды программы выполняет процессор. В нём несколько ядер, и каждое ядро выполняет одну последовательность команд за раз:

  • на четырёх ядрах четыре потока работают буквально одновременно;
  • потоков обычно намного больше, чем ядер, поэтому операционная система переключает ядро с одного потока на другой много раз в секунду;
  • со стороны кажется, что работают все сразу.

Кто и когда получит ядро, решает операционная система. Поэтому порядок, в котором работают потоки, не задан и меняется от запуска к запуску.

Процесс

Процесс — это запущенная программа со своей памятью. Браузер, музыкальный плеер и ваша программа на Java — три разных процесса. Операционная система отделяет их память друг от друга: один процесс не может прочитать переменные другого.

Команда java Main.java запускает один процесс — JVM, которая выполняет вашу программу. В нашей картинке процесс — это вся кухня с холодильником и доской, а соседний ресторан — другой процесс.

Поток

Поток — это последовательность команд, которая выполняется внутри процесса. Это повар на кухне: он идёт по своему рецепту строка за строкой. В одном процессе может работать много потоков, и все они пользуются памятью этого процесса.

Что у потоков общее

  • Объекты в куче — всё, что создано через new. Если у двух потоков есть ссылка на один объект, они работают с одним и тем же объектом.
  • static-поля. Они принадлежат классу, а класс в программе один.

Это холодильник и доска на стене: их видят все повара.

Что у каждого потока своё

  • Локальные переменные и параметры методов. Если два потока выполняют один и тот же метод, каждый получает свою копию его переменных.
  • Место в программе — строка, которую поток выполняет сейчас.

Это блокнот повара и его место в рецепте.

Чем процесс отличается от потока

Этот вопрос часто задают на собеседовании. Ответ складывается из трёх пунктов:

  • процесс — отдельная программа со своей памятью, а поток выполняется внутри процесса;
  • потоки одного процесса делят кучу и static-поля, а локальные переменные у каждого потока свои;
  • поток дешевле процесса: ему не нужно своё отдельное пространство памяти, поэтому его быстрее создать и на него быстрее переключиться.

Здесь аналогия с кухней неточна. Повара видят друг друга и могут договориться, кто подойдёт к доске первым. Потоки друг друга не видят: два потока могут взяться за один объект в одно и то же мгновение, и Java их не остановит.

Главный поток

Даже в программе, где вы не создавали ни одного потока, один поток есть. JVM выполняет main в потоке, который так и называется — main. Проверим:

JAVA
public class Main {
    public static void main(String[] args) {
        String name = Thread.currentThread().getName();
        System.out.println(name); // main
    }
}

Разберём по шагам:

  1. Thread — класс из стандартной библиотеки: каждый поток в Java представлен объектом этого класса.
  2. Thread.currentThread() — static-метод, который возвращает объект того потока, что выполняет эту строку.
  3. getName() возвращает имя потока, и программа печатает main.

Вы уже встречали это имя в строке Exception in thread "main": так JVM сообщает, что исключение случилось в главном потоке. Кроме main, JVM запускает несколько служебных потоков для своей работы, так что даже самая короткая программа на Java многопоточная.

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

Имя потока спросили у самого класса Thread:

JAVA
String name = Thread.getName(); // ✗
TEXT
error: non-static method getName() cannot be referenced from a static context

Программа не компилируется. getName() — метод объекта, а не класса: имя у каждого потока своё, и компилятору нужно знать, чьё именно. Сначала получите объект потока через Thread.currentThread(), а потом спросите его имя: Thread.currentThread().getName().

Главное

  • Процесс — запущенная программа со своей памятью, а поток — последовательность команд внутри процесса.
  • Потоки одного процесса делят объекты в куче и static-поля, а локальные переменные у каждого потока свои.
  • Порядок, в котором работают потоки, решает операционная система, и от запуска к запуску он разный.
  • main выполняется в главном потоке с именем main, а Thread.currentThread() возвращает поток, который выполняет текущую строку.