Co oznacza ten kod dołączania do wątków?


156

Co w tym kodzie oznaczają dwa łączenia i przerwy? t1.join()powoduje t2zatrzymanie do t1zakończenia?

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
Skoro będzie blokować i czekać na zakończenie wątku, dlaczego użyłeś pętli while?
Mehdi

@MahdiElMasaoudi Przypuszczam, że ma czekać, nawet jeśli wątek zostanie przerwany? Prawdopodobnie nie jest to świetny sposób na zrobienie tego
forresthopkinsa

Pamiętaj, aby zaakceptować odpowiedź tutaj, jeśli była pomocna.
Gray

Jak wspomniano, nie musisz while(true)wywoływać joinmetody.
Chaklader Asfak Arefe

Odpowiedzi:


311

Co oznacza ten kod dołączania do wątków?

Cytując z Thread.join()metody javadocs :

join() Czeka, aż ten wątek umrze.

Istnieje wątek, który uruchamia Twój przykładowy kod, który jest prawdopodobnie głównym wątkiem .

  1. Główny wątek tworzy i uruchamia wątki t1i t2. Dwa wątki zaczynają działać równolegle.
  2. Główny wątek wywołuje t1.join()oczekiwanie na zakończenie t1wątku.
  3. W t1finalizuje gwintu i t1.join()powraca metody z głównym gwintem. Zauważ, że t1mogło to już zakończyć się przed wykonaniem join()połączenia, w takim przypadkujoin() połączenie powróci natychmiast.
  4. Główny wątek wywołuje t2.join()oczekiwanie nat2wątku.
  5. W t2finalizuje wątek (lub może być zakończona, zanim t1zrobił nić) i t2.join()powraca metoda w głównym wątku.

Ważne jest, aby zrozumieć, że wątki t1i t2działały równolegle, ale główny wątek, który je rozpoczął, musi poczekać na zakończenie, zanim będzie mógł być kontynuowany. To powszechny wzorzec. Ponadto, t1i / lub t2mógł skończyć się przed wywołaniem join()ich przez główny wątek . Jeśli tak, to join()nie będzie czekać, ale wróci natychmiast.

t1.join() oznacza, że ​​t2 zatrzymuje się, aż t1 się kończy?

Nie. Główny wątek, który wywołuje t1.join(), przestanie działać i zaczeka na zakończenie t1wątku. t2Wątek jest uruchomiony równolegle i nie ma wpływu t1lub t1.join()rozmowa w ogóle.

Jeśli chodzi o try / catch, join()rzuty InterruptedExceptionoznaczają, że wywoływany wątek główny join()może sam zostać przerwany przez inny wątek.

while (true) {

Łączenia w whilepętli to dziwny wzór. Zazwyczaj pierwsze łączenie, a następnie drugie łączenie, obsługuje InterruptedExceptionodpowiednio w każdym przypadku. Nie ma potrzeby zapętlania ich.


24
+1 To bardzo dziwny wzór i prawdopodobnie można go usunąć.
m0skit0

3
Jeśli t1 kończy się jako pierwszy, to t2 kończy. Wydaje się, że jest to proces sekwencyjny. Jeden wątek kończy się jako pierwszy, potem drugi. jaki jest sens wielowątkowości?
user697911

9
Ponieważ t1i t2mogą działać równolegle. Chodzi tylko o to, że mainoboje muszą skończyć, zanim będzie można kontynuować. To typowy wzorzec @ user697911.
Gray

3
whilePętla jest tam, bo (chyba), że chce, aby ponowić próbę z join()połączenia jeśli zostanie przerwane? Na pewno nie napisałbym tego w ten sposób @ user697911.
szary

5
Pętla jest tam, aby zapewnić, że oba t1i t2zakończą się. To znaczy. jeśli t1wyrzuci InterruptedException, zapętli się i zaczeka na t2. Alternatywą jest czekanie na oba wątki w każdym Try-Catch, aby uniknąć pętli. Ponadto, w zależności od EventThreadtego, może to mieć sens, ponieważ uruchamiamy 2 wątki, a nie jeden.
Michael Bisbjerg

68

To jest ulubione pytanie do wywiadu 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()oznacza, że ​​t1 mówi coś w stylu „ Chcę najpierw skończyć ”. To samo dotyczy t2. Bez względu na to, kto rozpoczął t1lub t2wątek (w tym przypadku mainmetoda), main zaczeka na t1i t2zakończy swoje zadanie.

Jednak ważne jest, aby zanotować, t1a t2same mogą działać równolegle niezależnie od sekwencji łączenia połączeń na t1i t2. To main/daemonnić musi czekać .


3
Dobry przykład. O „może działać równolegle”: I co z tego? Ważne jest, aby główny wątek czekał NAJPIERW na t1, a NASTĘPNIE na t2. Naprawdę nie ma znaczenia, co robią t1 lub t2 (z perspektywy głównego wątku)
Alex

1
Wspomniałeś o wątku "main / daemon". Rozumiem, ale demon nie ma z tym nic wspólnego. Główny wątek nie jest demonem.
Gray

1
t1.join(); t2.join();nie pozwoli wątkowi, który wykonuje łączenia, na kontynuowanie, dopóki oba wątki nie zostaną zakończone. W przypadku braku bardzo nietypowego kodu w innym miejscu, kolejność łączenia nie ma znaczenia.
David Schwartz

Czy więc t2.join () zostanie wywołana dopiero po zakończeniu t1?
Leo Droidcoder

Innymi słowy, jeśli chcemy "serializować" wykonanie wątków t1 i t2, musimy umieścić t1.join () zaraz po t1.start (), ponieważ główny wątek zaczął t1 (a później t2) i oczywiście usunąć go z próbuj złapać. Oczywiście konsekwencją tego będzie utrata równoległości.
dobrivoje

47

join()oznacza oczekiwanie na zakończenie wątku. To jest metoda blokująca. Twój główny wątek (ten, który wykonuje join()) będzie czekał na t1.join()linii, aż t1zakończy swoją pracę, a następnie zrobi to samo dla t2.join().


29

Obraz jest wart tysiąca słów.

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

Mam nadzieję, że przydatne, aby uzyskać więcej informacji, kliknij tutaj


3
Jasne i precyzyjne.
dobrivoje

10

Gdy wątek tA wywołuje funkcję tB.join (), powoduje to nie tylko oczekiwanie na śmierć tB lub przerwanie tA, ale także utworzenie relacji zdarzają się przed ostatnią instrukcją w tB i następną po tB.join () w wątku tA.

Wszystkie akcje w wątku mają miejsce, zanim jakikolwiek inny wątek pomyślnie powróci z funkcji join () w tym wątku.

To znaczy program

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);
    }
}

Zawsze drukuj

>> 1111111111111111111111111 ...

Ale program

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);
    }
}

Może drukować nie tylko

>> 0000000000 ... 000000111111111111111111111111 ...

Ale

>> 00000000000000000000000000000000000000000000 ... 

Zawsze tylko „0”.

Ponieważ Java Memory Model nie wymaga „przenoszenia” nowej wartości „sharedVar” z wątkuB do głównego wątku bez relacji heppens-before (początek wątku, łączenie wątku, użycie słowa kluczowego „synchonized”, użycie zmiennych AtomicXXX itp.).


5

Mówiąc najprościej:
t1.join()wraca po t1zakończeniu.
Nie robi nic w wątku t1, z wyjątkiem czekania na zakończenie.
Oczywiście następujący kod t1.join()zostanie wykonany dopiero po t1.join()zwrocie.


1
zostanie wykonany dopiero po tym, jak t1.join () zwróci +1
mohsen.nour

3

Ze strony dokumentacji Oracle na temat połączeń

joinMetoda pozwala gwint czekać na zakończenie drugiego.

Jeśli t1 jest Threadobiektem, którego wątek jest aktualnie wykonywany,

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

Jeśli t2 jest Threadobiektem, którego wątek jest aktualnie wykonywany,

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

joinAPI to niskopoziomowe API, które zostało wprowadzone we wcześniejszych wersjach java. Wiele rzeczy zostało zmienionych przez pewien czas (szczególnie w wydaniu jdk 1.5) na froncie współbieżności.

To samo można osiągnąć dzięki interfejsowi API java.util.concurrent. Oto niektóre przykłady

  1. Używanie invokeAll onExecutorService
  2. Korzystanie z CountDownLatch
  3. Korzystanie z ForkJoinPool lub newWorkStealingPool of Executors(od wersji java 8)

Zobacz powiązane pytania dotyczące SE:

poczekaj, aż wszystkie wątki zakończą swoją pracę w java


1

Dla mnie zachowanie Join () zawsze było mylące, ponieważ starałem się zapamiętać, kto będzie na kogo czekać. Nie próbuj o tym pamiętać w ten sposób.

Poniżej znajduje się czysty mechanizm wait () i notify ().

Wszyscy wiemy, że gdy wywołujemy funkcję wait () na dowolnym obiekcie (t1), wywołanie obiektu (main) jest wysyłane do poczekalni (stan Zablokowany).

Tutaj główny wątek wywołuje join (), czyli wait () pod osłonami. Więc główny wątek będzie czekał, aż zostanie powiadomiony. Powiadomienie jest wysyłane przez t1, gdy zakończy swoje działanie (zakończenie wątku).

Po otrzymaniu powiadomienia main wychodzi z poczekalni i przystępuje do realizacji.


0

Mam nadzieję, że to pomoże!

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

powiedzmy, że nasz główny wątek rozpoczyna wątki t1 i t2. Teraz, gdy wywoływana jest funkcja t1.join (), główny wątek zatrzymuje się, aż wątek t1 umrze, a następnie sam się wznawia. Podobnie, gdy t2.join () jest wykonywana, główny wątek ponownie się zawiesza, aż wątek t2 umrze, a następnie zostanie wznowiony.

A więc tak to działa.

Ponadto pętla while nie była tutaj naprawdę potrzebna.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.