Що означає цей код приєднання потоку?


156

У цьому коді, що означає два об'єднання та розрив? t1.join()причини t2зупиняються, поки не t1припиняються?

Thread t1 = new Thread(new EventThread("e1"));
t1.start();
Thread t2 = new Thread(new EventThread("e2"));
t2.start();
while (true) {
   try {
      t1.join();
      t2.join();
      break;
   } catch (InterruptedException e) {
      e.printStackTrace();
   }
}


3
Оскільки він буде блокувати і чекати завершення потоку, чому ви використовували цикл while?
Мехді

@MahdiElMasaoudi Я вважаю, щоб чекати, навіть якщо нитка перервана? Напевно, це не чудовий спосіб зробити це
forresthopkinsa

Не забудьте прийняти відповідь тут, якщо це було корисно.
Сірий

Як згадувалося, вам не потрібно while(true)викликати joinметод.
Chaklader Asfak Arefe

Відповіді:


311

Що означає цей код приєднання потоку?

Цитувати з Thread.join()методу javadocs :

join() Чекає, коли ця нитка загине.

Є потік, на якому працює ваш приклад, який, мабуть, є основним потоком .

  1. Основна нитка створює і запускає t1і t2потоки. Дві нитки починають працювати паралельно.
  2. Основний потік закликає t1.join()чекати, коли t1нитка закінчиться.
  3. На t1нитки завершується , і t1.join()метод повертає в головному потоці. Зверніть увагу, що t1це вже могло закінчитися до join()виклику, і в цьому випадку join()виклик негайно повернеться.
  4. Основний потік закликає t2.join()чекати, коли t2нитка закінчиться.
  5. У t2завершує нитка (або це може бути завершено до того , як t1нитка зробила) і t2.join()повертає метод в основному потоці.

Важливо розуміти, що t1і t2потоки працюють паралельно, але головна нитка, яка їх запустила, повинна зачекати, поки вони закінчуються, перш ніж вона може продовжуватися. Це загальна закономірність. Також t1та / або t2могли завершитися, перш ніж основний потік викликає join()їх. Якщо так, то join()не чекатиме, але повернеться негайно.

t1.join() означає спричинити зупинку t2, поки t1 не припиняється?

Ні. Основна нитка, яка викликає t1.join(), перестане працювати і чекатиме завершення t1потоку. t2Нитка працює паралельно і не впливає t1або t1.join()виклик на всіх.

З точки зору спроби / лову, join()кидки InterruptedExceptionозначають, що головна нитка, яка викликає, join()сама по собі може бути перервана іншим потоком.

while (true) {

Наявність з'єднань у whileциклі - дивна закономірність. Зазвичай ви робите перше з'єднання, а потім друге з'єднання, обробляючи InterruptedExceptionналежним чином у кожному конкретному випадку. Не потрібно їх класти в петлю.


24
+1 Це дуже дивна картина, і, ймовірно, її можна видалити.
m0skit0

3
Якщо t1 закінчити спочатку, то t2 закінчити. Це здається послідовним процесом. Одна нитка закінчується спочатку, потім інша. у чому сенс багаторазової нарізки?
user697911

9
Тому що t1і t2може працювати паралельно. Просто вони mainпотребують обох закінчити, перш ніж це може продовжуватися. Це типова модель @ user697911.
Сірий

3
whileПетля там , тому що (я думаю) він хоче повторити ті join()виклики , якщо один переривається? Я, звичайно, не писав би це так @ user697911.
Сірий

5
Петля є для того, щоб забезпечити і те, t1і t2закінчити. Тобто якщо t1викине InterruptedException, він повернеться назад і чекатиме t2. Альтернативно - чекати обох потоків у кожному своєму Try-Catch, щоб циклу можна було уникнути. Також, в залежності від EventThreadцього, може бути сенс зробити це так, оскільки ми виконуємо 2 теми, а не одну.
Майкл Бісб'єрг

68

Це улюблене питання інтерв'ю Java .

Thread t1 = new Thread(new EventThread("e1"));
t1.start();
Thread e2 = new Thread(new EventThread("e2"));
t2.start();

while (true) {
    try {
        t1.join(); // 1
        t2.join(); // 2  These lines (1,2) are in in public static void main
        break;
    }
}

t1.join()означає, t1 говорить щось на кшталт " я хочу закінчити першим ". Так само і у випадку з t2. Незалежно від того , хто почав t1або t2нитка (в даному випадку mainметод), основний чекатиме до тих пір , t1і t2закінчити свою задачу.

Однак важливий момент, який слід зазначити, t1і t2вони можуть працювати паралельно незалежно від послідовності з'єднання дзвінків на t1та t2. Саме main/daemonнитка повинна чекати .


3
Хороший приклад. Про "може працювати паралельно": Так що? Важливо, щоб основна нитка зачекала ПЕРШИЙ t1, а потім Т2. Це дійсно не має значення, чим займаються t1 або t2 (з точки зору основної теми)
Alex

1
Ви згадуєте "головну / демонову" нитку. Основне я розумію, але демон не має нічого спільного. Основна нитка - недемон.
Сірий

1
t1.join(); t2.join();не дозволить продовження потоку, який виконує з'єднання, доти, поки обидва потоки не закінчуються. За відсутності дуже незвичного коду в інших місцях, порядок з'єднань не має значення.
Девід Шварц

Так t2.join () буде називатися лише тоді, коли закінчиться t1?
Лев Droidcoder

Іншими словами, якщо ми хочемо "серіалізувати" виконання потоків t1 і t2, нам потрібно розмістити t1.join () відразу після t1.start (), оскільки головна нитка запустилася t1 (і t2 після цього) і, звичайно, видалити її з спробувати / зловити. Очевидно, що робити це, наслідком буде втрата паралелізму.
dobrivoje

47

join()означає чекати завершення теми. Це метод блокатора. Ваш основний потік (той, що робить join()) буде чекати на t1.join()рядку, поки не t1закінчить свою роботу, а потім зробить те ж саме для t2.join().


29

Малюнок вартує тисячі слів.

    Main thread-->----->--->-->--block##########continue--->---->
                 \                 |               |
sub thread start()\                | join()        |
                   \               |               |
                    ---sub thread----->--->--->--finish    

Сподіваємось на корисне, для більш детальної інформації натисніть тут


3
Чіткий і точний.
dobrivoje

10

Коли потік tA викликає tB.join (), його причини не тільки чекають, коли tB помер, або tA перерветься сам, але створить попереднє відношення між останньою заявою в tB і наступною операцією після tB.join () в потоці tA.

Усі дії в потоці відбуваються, перш ніж будь-який інший потік успішно повертається з приєднання () до цього потоку.

Це означає програму

class App {
    // shared, not synchronized variable = bad practice
    static int sharedVar = 0;
    public static void main(String[] args) throws Exception {
        Thread threadB = new Thread(() -> {sharedVar = 1;});
        threadB.start();
        threadB.join();

        while (true) 
            System.out.print(sharedVar);
    }
}

Завжди друкуйте

>> 1111111111111111111111111 ...

Але програма

class App {
    // shared, not synchronized variable = bad practice
    static int sharedVar = 0;
    public static void main(String[] args) throws Exception {
        Thread threadB = new Thread(() -> {sharedVar = 1;});
        threadB.start();
        // threadB.join();  COMMENT JOIN

        while (true)
            System.out.print(sharedVar);
    }
}

Може друкувати не тільки

>> 0000000000 ... 000000111111111111111111111111 ...

Але

>> 00000000000000000000000000000000000000000000 ... 

Завжди лише "0".

Оскільки модель пам'яті Java не вимагає "перенесення" нового значення "sharedVar" з ниткиB в основний потік без відношення heppens-before (запуск потоку, з'єднання потоку, використання "синхронізованого" ключового слова, використання змінних AtomicXXX тощо).


5

Простіше кажучи:
t1.join()повертається після t1завершення.
Це не робить нічого, щоб зафіксувати тему t1, крім чекання, коли воно закінчиться.
Звичайно, наступний код t1.join()буде виконуватися лише після t1.join()повернення.


1
буде виконано лише після того, як t1.join () поверне +1
mohsen.nour

3

З сторінки документації oracle на сторінці Joins

joinМетод дозволяє один потоку чекати завершення інших.

Якщо t1 - Threadоб'єкт, поток якого зараз виконується,

t1.join() : causes the current thread to pause execution until t1's thread terminates.

Якщо t2 - Threadоб'єкт, поток якого зараз виконується,

t2.join(); causes the current thread to pause execution until t2's thread terminates.

joinAPI - це API низького рівня, який був представлений у більш ранніх версіях Java. Багато часу було змінено протягом певного періоду часу (особливо з випуском jdk 1.5) на фронті одночасності.

Це можна досягти за допомогою API java.util.concurrent. Деякі приклади є

  1. Використання invokeAll увімкненоExecutorService
  2. Використання CountDownLatch
  3. Використання ForkJoinPool або newWorkStealingPool of Executors(з Java 8)

Зверніться до відповідних питань SE:

зачекайте, поки всі теми закінчать свою роботу в Java


1

Для мене поведінка Join () завжди була заплутаною, бо я намагався згадати, хто кого чекатиме. Не намагайтеся запам'ятати це таким чином.

Внизу це чистий механізм wait () та notify ().

Ми всі знаємо, що, коли ми викликаємо wait () на будь-якому об’єкті (t1), викликуючий об'єкт (основний) відправляється в зал очікування (заблокований стан).

Тут головний потік викликає join (), який є wait () під обкладинками. Тож головна нитка буде чекати, поки вона буде повідомлена. Повідомлення дає t1, коли він закінчує його виконання (завершення потоку).

Після отримання повідомлення головний виходить із залу очікування та продовжує його виконання.


0

Сподіваюся, це допомагає!

package join;

public class ThreadJoinApp {

    Thread th = new Thread("Thread 1") {
        public void run() {
            System.out.println("Current thread execution - " + Thread.currentThread().getName());
            for (int i = 0; i < 10; i++) {
                System.out.println("Current thread execution - " + Thread.currentThread().getName() + " at index - " + i);
            }
        }
    };

    Thread th2 = new Thread("Thread 2") {
        public void run() {
            System.out.println("Current thread execution - " + Thread.currentThread().getName());

            //Thread 2 waits until the thread 1 successfully completes.
            try {
            th.join();
            } catch( InterruptedException ex) {
                System.out.println("Exception has been caught");
            }

            for (int i = 0; i < 10; i++) {
                System.out.println("Current thread execution - " + Thread.currentThread().getName() + " at index - " + i);
            }
        }
    };

    public static void main(String[] args) {
        ThreadJoinApp threadJoinApp = new ThreadJoinApp();
        threadJoinApp.th.start();
        threadJoinApp.th2.start();
    }

    //Happy coding -- Parthasarathy S
}

-3

скажімо, наша основна нитка починає нитки t1 і t2. Тепер, коли викликається t1.join (), головна нитка призупиняється, поки нитка t1 не відмирає, а потім відновлюється. Аналогічно, коли t2.join () виконується, головна нитка знову призупиняється, поки потік t2 не відмирає і потім відновлюється.

Отже, ось як це працює.

Також цикл у той час тут не дуже потрібен.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.