JavaRush/Java блог/Java Developer/Многопоточность: что делают методы класса Thread
Автор
Pavlo Plynko
Java-разработчик в CodeGym

Многопоточность: что делают методы класса Thread

Статья из группы Java Developer
участников
Привет! Сегодня продолжаем говорить о многопоточности. Рассмотрим класс Thread и работу его нескольких методов. Раньше, когда мы изучали методы класса, чаще всего просто писали это так: «название метода» -> «что он делает». С методами Thread так не получится :) Их логика сложнее, и без нескольких примеров не разобраться. Если коротко: класс Thread управляет потоками через несколько ключевых методов. start() запускает поток (именно start(), а не run()), sleep(мс) усыпляет текущий поток на заданное время, join() заставляет текущий поток дождаться завершения другого, а interrupt() просит поток прерваться, isInterrupted() проверяет флаг прерывания. Устаревший stop() использовать нельзя: он останавливал поток небезопасно.

Кратко

  • start() запускает поток и вызывает его run() в новом потоке; прямой вызов run() выполняется в текущем потоке, без многопоточности.
  • sleep(мс) это статический метод: усыпляет текущий поток на заданное число миллисекунд и может быть прерван (InterruptedException).
  • Проснувшийся поток переходит в состояние runnable, но продолжит работу не мгновенно: момент решает планировщик.
  • join() приостанавливает текущий поток, пока не завершится указанный поток; помогает задать порядок выполнения.
  • interrupt() прерывает ожидание (InterruptedException) или ставит флаг interrupted; корректное завершение реализует сам программист через isInterrupted().
  • Метод stop() устарел (deprecated) и небезопасен: он обрывал поток без освобождения ресурсов.

Метод Thread.start()

Начнем с повторения. Как ты наверняка помнишь, создать поток можно унаследовав свой класс от класса Thread и переопределив в нем метод run(). Но сам он, конечно, не запустится. Для этого у нашего объекта вызываем метод start(). Запуск потока методом start(), а не run()Давай вспомним пример из предыдущей лекции:
public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Выполнен поток " + getName());
   }
}


public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {
           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Обрати внимание: чтобы запустить поток, необходимо вызвать специальный метод start(), а не метод run()! Эту ошибку легко допустить, особенно в начале изучения многопоточности. Если в нашем примере ты 10 раз вызовешь у объекта метод run() вместо start(), результат будет таким:
public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {
           MyFirstThread thread = new MyFirstThread();
           thread.run();
       }
   }
}
Выполнен поток Thread-0 Выполнен поток Thread-1 Выполнен поток Thread-2 Выполнен поток Thread-3 Выполнен поток Thread-4 Выполнен поток Thread-5 Выполнен поток Thread-6 Выполнен поток Thread-7 Выполнен поток Thread-8 Выполнен поток Thread-9 Посмотри на последовательность вывода: все идет строго по порядку. Странно, да? Мы к такому не привыкли, ведь уже знаем, что порядок запуска и выполнения потоков определяет сверхразум внутри нашей операционной системы, планировщик потоков. Может, просто повезло? Конечно, дело не в везении. В этом можешь убедиться, запустив программу еще пару раз. Дело в том, что прямой вызов метода run() не имеет отношения к многопоточности. В этом случае программа будет выполнена в главном потоке, том, в котором выполняется метод main(). Он просто последовательно выведет 10 строк на консоль и все. Никакие 10 потоков не запустятся. Поэтому запомни на будущее и постоянно себя проверяй. Хочешь, чтобы выполнился run(), вызывай start(). Поехали дальше.

Метод Thread.sleep()

Для приостановки выполнения текущего потока на какое-то время, используем метод sleep(). Метод sleep() усыпляет текущий поток на заданное времяМетод sleep() принимает в качестве параметра число миллисекунд, то есть то время, на которое необходимо «усыпить» поток.
public class Main {

   public static void main(String[] args) throws InterruptedException {

       long start = System.currentTimeMillis();

       Thread.sleep(3000);

       System.out.println(" - Сколько я проспал? \n - " + ((System.currentTimeMillis()-start)) / 1000 + " секунды");

   }
}
Вывод в консоль: - Сколько я проспал? - 3 секунды Обрати внимание: метод sleep() статический: он усыпляет текущий поток. То есть тот, который работает в данный момент. Еще один важный нюанс: поток в состоянии сна можно прервать. В таком случае в программе возникнет исключение InterruptedException. Мы рассмотрим пример ниже. Кстати, а что произойдет после того, как поток «проснется»? Продолжит ли он сразу же свое выполнение с того места, где закончил? Нет. После того, как поток «просыпается», когда заканчивается время, переданное в качестве аргумента в Thread.sleep(), он переходит в состояние runnable, «работоспособный». Однако это не значит, что планировщик потоков запустит именно его. Вполне возможно, он отдаст предпочтение какому-то другому «неспящему» потоку, а наш «свежепроснувшийся» продолжит работу чуть позже. Обязательно запомни: «проснулся, не значит продолжил работать в ту же секунду»!

Метод Thread.join()

Метод join() заставляет поток дождаться завершения другого потокаМетод join() приостанавливает выполнение текущего потока до тех пор, пока не завершится другой поток. Если у нас есть 2 потока, t1 и t2, и мы напишем:
t1.join()
t2 не начнет работу, пока t1 не завершит свою. Метод join() можно использовать, чтобы гарантировать последовательность выполнения потоков. Давай рассмотрим работу join() на примере:
public class ThreadExample extends Thread {

   @Override
   public void run() {

       System.out.println("Начало работы потока " + getName());

       try {
           Thread.sleep(5000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("Поток " + getName() +  " завершил работу.");
   }
}


public class Main {

   public static void main(String[] args) throws InterruptedException {

       ThreadExample t1 = new ThreadExample();
       ThreadExample t2 = new ThreadExample();

       t1.start();


 /*Второй поток t2 начнет выполнение только после того, как будет завершен
       (или бросит исключение) первый поток - t1*/
       try {
           t1.join();
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

       t2.start();

       //Главный поток продолжит работу только после того, как t1 и t2 завершат работу
       try {
           t1.join();
           t2.join();
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

       System.out.println("Все потоки закончили работу, программа завершена");

   }
}
Мы создали простой класс ThreadExample. Его задача вывести на экран сообщение о начале работы, потом уснуть на 5 секунд и в конце сообщить о завершении работы. Ничего сложного. Главная логика заключена в классе Main. Посмотри на комментарии: с помощью метода join() мы успешно управляем последовательностью выполнения потоков. Если ты вспомнишь начало темы, этим занимался планировщик потоков. Он запускал их на свое усмотрение: каждый раз по-разному. Здесь же мы с помощью метода гарантировали, что сначала будет запущен и выполнен поток t1, затем t2, и только после них главный поток выполнения программы. Идем дальше. В реальных программах тебе не раз встретятся ситуации, когда необходимо будет прервать выполнение какого-то потока. Например, наш поток выполняется, но при этом ждет определенного события или выполнения условия. Если это произошло, он останавливается. Было бы, наверное, логично, если бы существовал какой-то метод типа stop(). Однако все не так просто. Когда-то давно метод Thread.stop() в Java действительно был и позволял прерывать работу потока. Но позже его объявили устаревшим (deprecated). Ты можешь найти его в документации Oracle и увидеть, что он помечен как deprecated. Почему? Потому что он просто останавливал поток без какой-либо дополнительной работы. Например, поток мог работать с данными и что-то в них менять. Потом его резко вырубали методом stop() посреди работы, и все. Ни корректного завершения работы, ни освобождения ресурсов, ни хотя бы обработки ошибок, ничего этого не было. Метод stop(), если утрировать, просто крушил все на своем пути. Его работу можно сравнить с тем, как кто-то выдергивает вилку из розетки, чтобы выключить компьютер. Да, нужного результата добиться можно. Но все понимают, что через пару недель компьютер не скажет за это «спасибо». По этой причине логику прерывания потоков в Java изменили, и теперь используется специальный метод interrupt().

Метод Thread.interrupt()

Что произойдет, если у потока вызвать метод interrupt()? Есть 2 варианта:
  1. Если объект находился в этот момент в состоянии ожидания, например, join или sleep, ожидание будет прервано, и программа выбросит InterruptedException.
  2. Если же поток в этот момент был в работоспособном состоянии, у объекта будет установлен boolean-флаг interrupted.
Но проверить объект на значение этого флага и корректно завершить работу мы должны будем самостоятельно! Для этого в классе Thread есть специальный метод boolean isInterrupted(). Давай вернемся к примеру с часами, который был в лекции основного курса. Для удобства он немного упрощен:
public class Clock extends Thread {

   public static void main(String[] args) throws InterruptedException {
       Clock clock = new Clock();
       clock.start();

       Thread.sleep(10000);
       clock.interrupt();
   }

   public void run() {
       Thread current = Thread.currentThread();

       while (!current.isInterrupted())
       {
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               System.out.println("Работа потока была прервана");
               break;
           }
           System.out.println("Tik");
       }
   }
}
В нашем случае часы стартуют и начинают тикать каждую секунду. На 10-й секунде мы прерываем поток часов. Как ты уже знаешь, если поток, который мы пытаемся прервать, находится в одном из состояний ожидания, это приводит к InterruptedException. Данный вид исключения проверяемый, поэтому его можно легко перехватить и выполнить нашу логику завершения программы. Что мы и сделали. Вот наш результат: Tik Tik Tik Tik Tik Tik Tik Tik Tik Работа потока была прервана На этом мы заканчиваем знакомство с основными методами класса Thread.

Методы класса Thread: сводка

Все разобранные методы в одной таблице.
МетодЧто делаетСтатический?Ключевые нюансы
start()Запускает поток и вызывает его run() в новом потокенетЗапускать поток нужно им, а не run()
run()Содержит логику потоканетПрямой вызов выполняется в текущем потоке, без многопоточности
sleep(мс)Усыпляет текущий поток на заданное времядаМожет быть прерван, бросает InterruptedException
join()Ждет завершения другого потоканетЗадает порядок выполнения потоков
interrupt()Просит поток прерватьсянетПрерывает ожидание (InterruptedException) или ставит флаг interrupted
isInterrupted()Проверяет флаг прерываниянетВозвращает boolean; корректное завершение пишет сам программист
stop()Останавливал поток (устарел)даdeprecated и небезопасен, с Java 20 бросает UnsupportedOperationException
Чтобы закрепить знания, можешь посмотреть эту видеолекцию о многопоточности:
она послужит отличным дополнительным материалом! В конце, после обзора методов, в ней рассказывается как раз о том, что мы будем проходить дальше по курсу :) Успехов!

Вопросы и ответы

Чем метод start() отличается от run()?

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

Что делает метод Thread.sleep()?

Это статический метод: он усыпляет текущий поток на заданное число миллисекунд. Сон можно прервать, тогда возникнет InterruptedException.

Продолжит ли поток работу сразу после сна?

Нет. После пробуждения поток переходит в состояние runnable, но когда именно он продолжит работу, решает планировщик потоков. Может сначала поработать другой поток.

Для чего нужен метод join()?

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

Как правильно прерывать поток вместо stop()?

Методом interrupt(): он прерывает ожидание (InterruptedException) или ставит флаг interrupted. Проверку флага через isInterrupted() и корректное завершение работы программист реализует сам.

Почему нельзя использовать Thread.stop()?

Он устарел (deprecated) и небезопасен: обрывал поток посреди работы, не освобождая ресурсы и не завершая логику корректно. Начиная с Java 20 вызов stop() выбрасывает UnsupportedOperationException. Источник: Oracle: класс Thread.

Читайте также

Комментарии (248)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
TimJohnzz
Уровень 22
1 октября, 07:06
Спасибо, хорошая лекция
Grigoryvvv
Уровень 28
Expert
6 сентября, 23:15
07.09.2026 / 26 уровень
25 февраля, 10:27
Как по мне, автор написал спорные утверждения о методах join и interrupt.
VictorThe main expert in Automa в Строительная компани
14 апреля 2025, 12:02
Целый день смотрел последнюю лекцию - куча времени уходит, да ещё все примеры приходилось набирать вручную. Рекомендацию на просмотр считаю скорее вредной чем полезной.
Vitalii Shevchenko
Уровень 1
7 мая 2025, 09:27
Вчора переглядав цю лекцію на ютуб за власною ініціативою, а сьогодні зустрів посилання на неї тут. Хто хоче розібраться в багатопотоковості, однозначно рекомендую для перегляду (там ще 2-га частина є). Якщо проходите JavaRush аби лише ще один рівень в копілочку покласти, то біжіть далі😄
Dmitry Dvoeglazov
Уровень 30
7 апреля 2025, 14:39
ну вроде не поток, а нить. потоки это другое )
Eternal Fire
Уровень 1
29 июня 2025, 10:24
Главное на собеседовании такое не скажи
27 февраля 2025, 15:17
клевый видос заграбастал
Андрии Бумер
Уровень 41
11 февраля 2025, 21:34
Чергове підтвердженння що по відео вчитись в рази довше. те що можна прочитати за 2 хв, на відео триває 25 хв. Бонус плюс курсу джава раш, мінус що таке відео поклали. Звісно воно закріпляє інформацію і для когось це потрібно. Але тим хто нормально порозумів тему раджу дивитись десь з 30 хвилини.
Anonymous #3466634
Уровень 34
15 января 2025, 14:12
в самом начале говорили, что правильно называть не поток, а нить. И сами же называют потоками(
Олег Сычев Java Developer
10 мая 2025, 07:18
Их по разному называют на практике - потоки/нити/трэды. При чем, в обучающих материалах чаще слышал "Потоки" (с нитями впервые столкнулся на JavaRush), в работе чаще слышал "Трэды".
ssidijx
Уровень 30
3 декабря 2024, 20:57
подскажите зачем мы вызываем break; в catch блоке? в лекциях до этого так не делали и цикл while(!Thread.currentThread().isInterrupred()) нормально завершался при вызове у нити interrupt()
cherymara
Уровень 1
8 декабря 2024, 09:15
Потому что после блока catch еще идет вывод в консоль, который при прерывании уже не должен выводиться. Если бы break не было в этом месте, то после блока catch еще выполнился бы вывод в консоль tik
6aHguTo
Уровень 36
25 октября 2024, 14:25
зачем второй раз прописывать t1.join(); ?? ведь первый поток уже закончил работу ПЕРЕД стартом второго потока
28 октября 2024, 12:47
я так понял что это только второй поток в курсе что первый завершился, поэтому для главного потока еще раз написали. Хотя да, смысла в этом как будто нету
Сергій
Уровень 44
Expert
5 ноября 2024, 11:33
Не факт що він завершився. Виклик "t1.join()" відбувається в блоці "try" а в випадку помилки робота буде продовжена і буде виведено стрек помилок, але поток №1 може бути не завершений