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

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

Статья из группы Java Developer
участников
Привет! Сегодня продолжаем говорить о многопоточности. Рассмотрим класс Thread и работу его нескольких методов. Раньше, когда мы изучали методы класса, чаще всего просто писали это так: «название метода» -> «что он делает».
Многопоточность: что делают методы класса Thread - 1
С методами Thread так не получится :) Их логика сложнее, и без нескольких примеров не разобраться.

Метод Thread.start()

Начнем с повторения. Как ты наверняка помнишь, создать поток можно унаследовав свой класс от класса Thread и переопределив в нем метод run(). Но сам он, конечно, не запустится. Для этого у нашего объекта вызываем метод start(). Многопоточность: что делают методы класса Thread - 2Давай вспомним пример из предыдущей лекции:
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(). Многопоточность: что делают методы класса Thread - 3Метод 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()

Многопоточность: что делают методы класса Thread - 4Метод 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 действительно был и позволял прерывать работу потока. Но позже его удалили из библиотеки Java. Ты можешь найти его в документации 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. Чтобы закрепить знания, можешь посмотреть эту видеолекцию о многопоточности:
она послужит отличным дополнительным материалом! В конце, после обзора методов, в ней рассказывается как раз о том, что мы будем проходить дальше по курсу :) Успехов!
Комментарии (247)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
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
Уровень 30
29 июня 2025, 10:24
Главное на собеседовании такое не скажи
27 февраля 2025, 15:17
клевый видос заграбастал
Андрии Бумер
Уровень 37
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
Уровень 18
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 може бути не завершений
{Java_Shark}
Уровень 36
15 октября 2024, 16:15
еще одна годная статья, автору респект!!!
Дмитрий
Уровень 66
Expert
14 июля 2024, 13:01
- это пример из статьи - это если заменить break на interrupt() - это если вообще убрать прерывание цикла из экспериментов я пришел в к выводу, что флаг boolean-флаг interrupted сначала при вызове метода clock.interrupt(); ставится а true, а затем снова сбрасывается в false, поэтому если в catch принудительно не завершить цикл через break, то можно уйти в бесконечный цикл. Если вызывать interrupt вместо break то можно получить еще дополнительное сообщение Tik, но цикл в итоге завершится, потому что тег проставится все таки в true и проверка в условии цикла не пройдет
Omar AkkulovJava Developer в Paysonix
19 июля 2024, 17:14
В недавних лекциях были замечания по подобному коду (это для примера из статьи), а именно, когда мы уходим в бесконечный цикл после обработки эксепшена. Технически, метод Thread.sleep(long millis), будет работать вот каким образом: В тот момент, когда наш поток 'clock' при слипе прерывает какой-то другой поток методом interrrupt() (в нашем случае это делает поток main), то ты можешь покопаться во внутренности самого метода sleep(), в нем написано "If any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown." - то есть, если какой-либо поток прервал текущий поток(а у нас его прервал поток main), то прерванное состояние текущего потока очищается при возникновении этого исключения. То есть, какой-то внешний поток прервал наш работающий('clock') -> в итоге выбрасывается InterruptedException и после выброса этого эксепшена, получается, что далее в нашем потоке(не в main, а в том, в котором и пробросился exception, то есть поток 'clock') обновляется boolean переменная interrupted до false(внутри catch() блока interrupted поле потока будет уже со значением false), именно поэтому он затем опять проваливается в бесконечный цикл, и именно поэтому, как один из вариантов остановки потока, мы можем использовать и break в catch() блоке, чтобы полностью стопануть его Надеюсь, что понятно объяснил
Дмитрий
Уровень 66
Expert
20 июля 2024, 13:25
да, спасибо, более понятно стало, при возникновении InteruptedException флаг всегда сбарсывается на false и требуется его либо принудительно снова перевести в true или просто через break завершать выполнение
Дмитрий
Уровень 66
Expert
14 июля 2024, 12:27
меня всегда вводит в ступор запись такого вида public class Clock extends Thread { public static void main(String[] args) throws InterruptedException { Clock clock = new Clock(); Мы пишем класс и сразу же в этом классе создаем объект этого класса, в какой последовательности вообще происходит выполнение такого кода? Сначала код компилируется, а когда он начинает выполняться и доходит до строки Clock clock = new Clock(); что происходит? Есть например очередность выполнения конструкторов при наследовании, а какая очередность выполнения кода будет при таком создании объектов того же класса, что мы создаем?
Pavlo PlynkoJava-разработчик в CodeGymExpert
1 августа 2024, 12:02
в моем понимании, любой код в статических методах, например в main, не имеет никакого отношения к ООП. Не важно в каком классе эти методы размещены. Представь что есть отдельный класс с этим методом main, и код выполняется в нем. Разницы не будет никакой. в строке Clock clock = new Clock(); ничего необычного не происходит: создается объект типа Clock (выполняется его конструктор и конструкторы родителей). Создается переменная ссылочного типа, и в нее записывается адрес созданного объекта. Наличие или отутствие метода main или других статических методов в классе Clock или в классах которые он наследует - никак не влияет на то что происходит в этой строке.