undefined

join — ожидание завершения нити

Java Core
6 уровень , 4 лекция
Открыта

— Привет, Амиго! Я смотрю, ты делаешь большие успехи в изучении нитей.

— Это оказалось не сложно.

— Это же отлично! Сегодня легкий урок, и тема этого урока – метод join.

Представь себе ситуацию: главная нить создала дочернюю нить для выполнения какого-то задания. Проходит время, и вот главной нити понадобились результаты работы той дочерней нити. А дочерняя нить еще не закончила свою работу. Что делать главной нити?

— Да, что делать главной нити?

— Для этого есть метод join. Смысл его в следующем. Одна нить ждет, пока полностью завершится работа второй нити:

Код Описание
class Printer implements Runnable
{
private String name;
public Printer(String name)
{
this.name = name;
}
public void run()
{
System.out.println("I’m " + this.name);
}
}
Класс, который реализует интерфейс Runnable.
public static void main(String[] args)
{
Printer printer1 = new Printer("Коля");
Thread thread1 = new Thread(printer1);
thread1.start();

thread1.join();
}
Главная нить создает дочернюю нить – объект thread1.

Затем запускает ее – вызов thread1.start();

И ждет ее завершения – thread1.join();

Одна нить может вызвать метод join у объекта второй нити. В результате первая нить (которая вызвала метод) приостанавливает свою работу до окончания работы второй нити (у объекта которой был вызван метод).

Тут стоит различать две вещи: есть, собственно, нить – отдельный процесс выполнения команд, а есть объект этой нити (объект Thread).

— И это все?

— Да.

— А зачем нужно создавать нить и сразу же ждать ее завершения?

— Сразу же может и не нужно. А вот спустя какое-то время это может и понадобится. Главная нить после запуска первой дочерней нити может раздать еще много заданий другим нитям (создав их и вызвав метод start), а потом все – работы ей больше не осталось, нужно обрабатывать результаты работы первой дочерней нити. В таких случаях, когда нужно обязательно дождаться завершения работы другой нити и нужно вызывать метод join.

— Понятно.

Комментарии (185)
Чтобы просмотреть все комментарии или оставить свой,
перейдите в полную версию
Роман 19 уровень
24 марта 2021
если в главном потоке объявить join, то останавливается только этот поток, а остальные работают, или все ждут?
Valua Sinicyn 41 уровень, Харьков
20 февраля 2021
Комментарий выше - неверный ! 1 - поток создается на объекте (важно для понимания вызовов методов, т.к, под капотом - объект вызывает метод, сам поток не может ничего вызвать, т.к., он нематериален (ничто не может вызвать что то)); 2 - функция join иными словами: поток просит другие потоки подождать окончание своего выполнения, т.е., "подождите пока я закончу"; 3 - метод run выполненяет логику потока - функционал потока; 4 - "каждый работающий поток, может создавать ....." - Серьезно ?? Нематериальный поток может что то создать ? Единственное назначение потока - передача данных, ничего создавать он не может, в принципе ! Да, и потоки бывают только "работающие", неработающих потоков не существует в природе. 5 - код выполняется сверху - вниз и справа - налево, а не как написал сабж выше. Итого. Один неправильно написал, другой скопипастил и "улучшил".
Руслан 17 уровень, Санкт-Петербург
16 февраля 2021
Копипаст хорошего комментария от Романа, в более удобочитаемой форме: Одна нить может вызвать метод join у объекта второй нити. В результате первая нить (которая вызвала метод) приостанавливает свою работу до окончания работы второй нити (у объекта которой был вызван метод). После этой фразы пришлось разбираться))) Правило: код выполняется слева направо и сверху вниз – это важно! У нас есть главный поток (ГП), который запускает и выполняет метод main() согласно Правилу. Если мы в main() создадим еще один поток и запустим его, т.е. ГП идет по коду строка за строкой и натыкается на создание и запуск нового потока (НП). С этого момента мы имеем 2 параллельно живущих потока, которые в себе исполняют код согласно Правилу. ГП выполнит у себя НП.start(), и пойдет выполнять следующую строку кода, написанного в main(). Тем временем, пока ГП выполняет свой main(), НП начинает выполнять свой метод main() (у него он называется run()). Оба этих метода main() у ГП и run() у НП будут выполняться параллельно, опять же согласно правилу. Каждый работающий поток может создавать в себе и запускать новые потоки, т.е. параллельно выполняющийся, согласно правилу, код. А теперь про join(). Представим, что ГП – это начальник, которому нужно сдать общий отчет, состоящий из отчетов разных отделов. Допустим, у нас 4 отдела (О1, О2, О3, О4) Чтобы правильно сдать общий отчет, ГП нужно получить отчет от каждого отдела и, только после того, как сдаст отчет последний отдел (независимо от порядкового номера), ГП может их объединить, дописать свое и сдать общий отчет. Если ГП будет делать все сам – это будет очень долго и другие операции он производить не сможет (координировать работу предприятия, а не только заниматься отчетом). Поэтому он принимает решение дать задание каждому отделу сдать свой отчет. Отделы выполняют разную работу, поэтому и время на отчеты у всех разное: у одного час, у другого день. Еще раз, общий отчет возможен только после того, как сдаст отчет последний отдел.
Руслан 17 уровень, Санкт-Петербург
16 февраля 2021
ГП.main() { ГП Координирует работу предприятия О1 – сдать отчет.start() --> у О1 начинается бурная работа, запускается О1.run(){делать отчет, сдать отчет} К ГП Постучалась секретарша, принесла ценное инфо (любой код) О2 – сдать отчет.start() --> у О2 начинается бурная работа, запускается О2.run(){делать отчет, сходить на обед, сдать отчет} О3 – сдать отчет.start() --> у О3 начинается бурная работа, запускается О3.run(){делать отчет, запустить еще несколько потоков, сделать им join(), сделать сводную по их данным, сдать отчет} ГП проводит Небольшое совещание (любой код). О4 – сдать отчет.start() --> у О4 начинается бурная работа, запускается О4.run(){делать отчет, сдать отчет} ГП пошел на обед ГП делает свою работу О1 сдал отчет Проверка отчета от О1 С этого момента начинается «Интересное» – у ГП заканчивается рабочий день, т.е. заканчиваются дела, которые ему нужно сегодня сделать, кроме сдачи общего отчета (помним, что его можно сдать только после того, как последний отдел сдаст свой отчет), но отделы доделывают еще свои отчеты. Далее код выполняется согласно описанию ниже (Интересное) O3.join() Проверка отчета от О3 O2.join() Проверка отчета от О2 O4.join() Проверка отчета от О4 Составление общего отчета Отправка общего отчета Завершение работы }
Руслан 17 уровень, Санкт-Петербург
16 февраля 2021
Интересное: У ГП подходит к концу рабочий день, но закончить его он не может, пока не сдаст общий отчет. ГП, чтобы не уйти домой раньше срока (не завершить свою работу), ставит себе будильник (join()), который бы срабатывал на пришедший от выбранного отдела (О3.join()) отчет и сам засыпает прямо на рабочем месте, т.к. делать он больше ничего не может и уйти тоже не может. Далее Сдает отчет О3. ГП просыпается, проверяет отчет от О3. Дальше смотрит, сдал ли отчет О2. О2 еще не сдал, поэтому ГП засыпает. Сдает отчет О4. ГП спит, т.к. будильник от О2 еще не прозвонил (код в main() выполняется по правилу). Сдает отчет О2. ГП просыпается, проверяет отчет от О2. Дальше смотрит, сдал ли отчет О4. О4 отчет сдал. ГП берет его данные, проверят их. На этом месте ГП остался один, т.к. О1-О4 завершили свою работу. ГП идет дальше построчно, согласно своего кода в main() и выполняет следующую строку кода – составляет общий отчет ГП отправляет общий отчет ГП завершает свою работу. Таким образом, метод join(), который вызывает ГП у отдела (потока), говорит ГП о том, что на этом месте кода нужно заснуть и ждать, пока не прозвенит будильник от заджойненого потока, эт.е. пока поток не завершит свою работу и не включит этот будильник. И только после этого ГП может приступать к выполнению следующей строки своего кода (метод main)
Alec I 16 уровень
24 января 2021
По сути, код исполнения в примере выше будет как: 1) Запуск main - общий запуск программы и запуск главного потока main, который должен начаться первым и должен закончиться последним из всех потоков. 2) Thread thread1 = new Thread(printer1); для создания потока надо создать объект типа Thread, что делается тут. Через конструктор передается ссылка на ранее созданный объект. Тут стандартная реалзиация (создания) потока через интерфейс Runnable. Шаблон создания, который просто надо знать. 3) thread1.start(); Запуск дочренего потока, так как запуск можно сделать только через метод start(), который по сути вызвоет метод run(), точку входа в дочерний поток. В этот момент, будет произведен вход в дочерний поток и реализация всего кода внутри метода run(), как обычно слева на право и сверху вниз. 4) В main методе, пойдет реализация кода в потоке main сверху вниз, так как за thread1.start(); сразу идет метод thread1.join(); то main поток остановитсья и будет ждать заврешения кода в дочернем потоке. Если бы за метдом thread1.start(); внизу был бы какой-то код, то он бы исполнялся далее до метода thread1.join();, например. В нашем случае, main ждем когда будет исполнен код в методе run() внутри дочернего потока. 5) По завершению кода в методе run(), дочерний поток прекратит свое существование и главный поток main далее начнет исполнять свой код. Так вы соблюдаете правило, что главный поток должен быть последним завершающим потоком. Несколько уроков назад была картинка про main поток и дочерние потоки, где было показано соотношение между потоками. main в Java это точка входа в программу и ее завершение. Дочерние потоки не могут существовать дольше чем метод и соответственно поток main, иначе программа просто зависнет. Эти методы join, sleep, isAlive как раз были созданы для того, чтоб контролировать соотношение между потоками.
Alec I 16 уровень
24 января 2021
На самом деле все еще проще. Есть главный поток main по умолчанию, который запускается при запуске программы. У него могут быть дочерние потоки, один, два и т.д. НО, все дочерние потоки должны закончиться до того, как завершит свою работу главный поток. Как только поток Main завершил свою работу, то и программа завершила свою работу. Далее, из правила выше следует зачем нужен метод join. Как один поток может знать, что другой уже закончился? Через метод join, как один из вариантов, либо через метод isAlive(). В примере выше, thread1.join(); означает, что я - главный main поток жду до тех пор, пока ты, дочерний поток не завершишь свою работу. Как только второй (дочерний) поток завершит свою работу, я - main поток продолжну свою работу. Так вы гарантируете, что у вас другие дочерние потоки завершат работу до конца потка main. Точка входа в дочерний поток - это метод run(), так же как только будет исполнен весь код в методе run() в рамках другого дочернего потока, то происходит возврат в главный Main поток. Можно заставить поток остановиться через метод sleep(), как - спи N мил секунд, так тоже можено контролировать потоки и как-то их выравнивать, но такой подход очень сложный и явный минус, вам надо считать мил секунды.
🦔 Виктор 20 уровень, Москва Expert
14 января 2021
«Правило: код выполняется слева направо и сверху вниз – это важно! У нас есть главный поток (ГП), который запускает и выполняет метод main() согласно Правилу. Если мы в main() создадим еще один поток и запустим его, т.е. ГП идет по коду строка за строкой и натыкается на создание и запуск нового потока (НП). С этого момента мы имеем 2 параллельно живущих потока, которые в себе исполняют код согласно Правилу. ГП выполнит у себя НП.start(), и пойдет выполнять следующую строку кода, написанного в main(). Тем временем, пока ГП выполняет свой main(), НП начинает выполнять свой метод main() (у него он называется run()). Оба этих метода main() у ГП и run() у НП будут выполняться параллельно, опять же согласно правилу. Каждый работающий поток может создавать в себе и запускать новые потоки, т.е. параллельно выполняющийся, согласно правилу, код. А теперь про join(). Представим, что ГП – это начальник, которому нужно сдать общий отчет, состоящий из отчетов разных отделов. Допустим, у нас 4 отдела (О1, О2, О3, О4) Чтобы правильно сдать общий отчет, ГП нужно получить отчет от каждого отдела и, только после того, как сдаст отчет последний отдел (независимо от порядкового номера), ГП может их объединить, дописать свое и сдать общий отчет. Если ГП будет делать все сам – это будет очень долго и другие операции он производить не сможет (координировать работу предприятия, а не только заниматься отчетом). Поэтому он принимает решение дать задание каждому отделу сдать свой отчет. Отделы выполняют разную работу, поэтому и время на отчеты у всех разное: у одного час, у другого день. Еще раз, общий отчет возможен только после того, как сдаст отчет последний отдел» © Роман. Всё получится!
Veygard 22 уровень, Москва
28 декабря 2020
Еще вариант создания потока через new Thread - через анонимный класс:

Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Реализация run");
            }
        });
Когда предполагаешь что у метода run будет простая реализация, и нет необходимости создавать отдельный класс в качестве "такса для нити".
Бабочка Алушка 17 уровень, Новокузнецк
24 декабря 2020
И так, на самом деле все просто! 1) У нас есть основной поток с которым мы работали ранее main 2) Для создания еще одного потока необходимо создать объект потока. А значит нужно создать класс унаследованный от Thread (нить).

public static class Potok extends Thread
3) В созданном классе описываем метод run() который запустится при включении потока. Почему именно run? - метод run() уже определен в интерфейсе Runnable который реализовывается в классе Thread, от которого в свою очередь мы и унаследовали наш созданный класс.

public static class Potok extends Thread
{
      public void run()
      {
         //Тут пишем что должен делать поток.
      }
}
4) И так: "шаблон"(класс) нашего потока создан. Теперь необходимо создать сам поток и запустить его.

 public static void main(String[] args) 
    {
        Potok TH1 = new Potok();//создаем наш объект (поток)
        TH1.start();//запускаем наш созданный поток
    }
Вот некоторые методы для управления потоком:

name.start();//Запуск потока
name.sleep(time);//заморозить поток на Time милисекунд (1000мс = 1 сек)
name.join();//Поток замораживается пока поток Name не закончит свою работу.
name.interrupt(); //Пометить поток как ненужный(окончание работы при первой возможности).
Надеюсь чем то помог)