1. Введение в многопоточность: класс Thread, интерфейс Runnable и жизненный цикл потока
Введение в многопоточность: класс Thread, интерфейс Runnable и жизненный цикл потока
Добро пожаловать в курс «Многопоточность в Java: от основ до java.util.concurrent». Это первая статья, в которой мы заложим фундамент для понимания того, как работают современные приложения. Мы разберем, чем поток отличается от процесса, как создать свой первый поток в Java и через какие стадии он проходит за время своей жизни.
Зачем нам нужна многопоточность?
Представьте, что вы готовите ужин. Вам нужно нарезать овощи, сварить суп и запечь мясо. Если вы будете делать это строго последовательно (сначала нарезали всё, потом поставили вариться суп и ждете, пока он сварится, и только потом ставите мясо), ужин будет готов к полуночи. Это — однопоточный подход.
В реальной жизни вы ставите суп на плиту, мясо в духовку, а пока они готовятся, режете салат. Вы выполняете несколько задач параллельно или конкурентно. Это и есть многопоточность.
В программировании многопоточность позволяет:
Процесс и Поток: в чем разница?
Прежде чем писать код, важно разграничить два понятия: процесс и поток (thread).
Процесс
Процесс — это экземпляр запущенной программы. Он имеет свое собственное адресное пространство в памяти, свои дескрипторы файлов и другие ресурсы. Процессы изолированы друг от друга. Если один процесс «упадет» с ошибкой, это обычно не влияет на другие процессы.Поток
Поток (или нить) — это легковесный процесс. Потоки существуют внутри процесса. Ключевая особенность: все потоки одного процесса делят общую память (кучу/Heap). Это делает обмен данными между ними быстрым, но создает почву для серьезных проблем синхронизации, о которых мы будем говорить в следующих статьях.!Визуализация различия между процессом и потоками, работающими в общем пространстве памяти
Создание потока в Java
В Java работа с многопоточностью встроена в само ядро языка. Существует два основных способа создать поток: наследование от класса Thread и реализация интерфейса Runnable.
Способ 1: Наследование от класса Thread
Класс java.lang.Thread — это основной класс, представляющий поток. Чтобы создать свой поток, нужно создать класс-наследник и переопределить метод run().
Способ 2: Реализация интерфейса Runnable
Интерфейс Runnable содержит всего один метод — run(). Это функциональный интерфейс, что позволяет использовать лямбда-выражения.
Что выбрать: Thread или Runnable?
В 99% случаев следует выбирать реализацию интерфейса Runnable. И вот почему:
Thread, вы больше не сможете наследоваться ни от какого другого класса. Реализуя Runnable, вы сохраняете гибкость.Runnable представляет собой задачу (что нужно сделать), а Thread — механизм исполнения (как это сделать). Это соответствует принципам чистого кода.ExecutorService, который принимает именно задачи (Runnable или Callable), а не объекты Thread.Главная ошибка новичка: start() vs run()
Обратите внимание на код запуска выше. Мы вызываем метод start(), хотя логика написана в методе run(). Почему?
thread.start(): Создает новый системный поток, выделяет ему стек и вызывает метод run() уже в этом новом* потоке. Это и есть многопоточность.
thread.run(): Просто выполняет метод run() в текущем* потоке, как обычный вызов метода. Никакого нового потока не создается, параллелизма нет.
> Никогда не вызывайте метод run() напрямую, если хотите запустить код в новом потоке.
Жизненный цикл потока (Thread Lifecycle)
Поток не просто «работает» или «не работает». Он проходит через несколько состояний, описанных в перечислении Thread.State.
!Диаграмма переходов состояний жизненного цикла потока в Java
Разберем эти состояния подробнее:
Thread существует), но метод start() еще не вызван. Это просто объект в памяти.start() поток переходит в это состояние. Важно понимать: в Java состояние RUNNABLE означает, что поток либо выполняется прямо сейчас, либо готов к выполнению и ждет, пока планировщик операционной системы выделит ему процессорное время.Object.wait(), Thread.join() или LockSupport.park().WAITING, но с ограничением по времени. Например, Thread.sleep(1000) или obj.wait(500).run() завершил выполнение (успешно или с исключением). Поток уничтожен, его нельзя запустить повторно.Базовые методы управления потоком
Рассмотрим два фундаментальных метода, которые влияют на состояния потока.
Thread.sleep(long millis)
Этот статический метод переводит текущий поток в состояние TIMED_WAITING на указанное количество миллисекунд. Он не освобождает захваченные ресурсы (мониторы), а просто «засыпает».
join()
Метод join() позволяет одному потоку ждать завершения другого. Если вы вызовете t.join() в главном потоке, то главный поток перейдет в состояние WAITING и будет ждать, пока поток t не перейдет в состояние TERMINATED.
Заключение
Сегодня мы познакомились с основами многопоточности в Java. Мы узнали, что потоки позволяют выполнять задачи параллельно, деля общую память процесса. Мы научились создавать потоки через Runnable и Thread, правильно их запускать через start() и разобрали их жизненный цикл.
В следующей статье мы углубимся в самую сложную тему многопоточности — синхронизацию ресурсов и проблему Race Condition.