¿Qué significa este código de unión de hilo?

156

En este código, ¿qué significan las dos uniones y la ruptura? t1.join()hace t2que pare hasta que t1termina?

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();
   }
}
usuario697911
fuente
3
Dado que bloqueará y esperará la terminación del hilo, ¿por qué usó el bucle while?
Mehdi
@MahdiElMasaoudi, supongo, ¿seguir esperando incluso si se interrumpe el hilo? Probablemente no sea una buena forma de hacerlo
Forresthopkinsa
Recuerde aceptar una respuesta aquí si fue útil.
Gris
Como se mencionó, no necesita while(true)llamar al joinmétodo.
Chaklader Asfak Arefe

Respuestas:

311

¿Qué significa este código de unión de hilo?

Para citar del Thread.join()método javadocs :

join() Espera a que este hilo muera.

Hay un hilo que ejecuta su código de ejemplo, que probablemente sea el hilo principal .

  1. El hilo principal crea e inicia los hilos t1y t2. Los dos hilos comienzan a ejecutarse en paralelo.
  2. El hilo principal llama t1.join()a esperar que el t1hilo termine.
  3. El t1hilo se completa y el t1.join()método regresa en el hilo principal. Tenga en cuenta que t1ya podría haber finalizado antes de realizar la join()llamada, en cuyo caso la join()llamada volverá de inmediato.
  4. El hilo principal llama t2.join()a esperar que el t2hilo termine.
  5. El t2hilo se completa (o podría haberse completado antes que el t1hilo) y el t2.join()método regresa en el hilo principal.

Es importante comprender que los subprocesos t1y se t2han estado ejecutando en paralelo, pero el subproceso principal que los inició debe esperar a que finalicen antes de que pueda continuar. Ese es un patrón común. Además, t1y / o t2podría haber terminado antes de que el hilo principal los llame join(). Si es así join(), no esperará pero volverá de inmediato.

t1.join() significa hacer que t2 se detenga hasta que t1 termine?

No. El hilo principal que llama t1.join()dejará de funcionar y esperará t1a que termine el hilo. El t2hilo se ejecuta en paralelo y no se ve afectado por t1la t1.join()llamada ni en absoluto.

En términos de try / catch, los join()lanzamientos InterruptedExceptionsignifican que el hilo principal que está llamando join()puede ser interrumpido por otro hilo.

while (true) {

Tener las uniones en un whilebucle es un patrón extraño. Por lo general, haría la primera combinación y luego la segunda combinación manejando InterruptedExceptionadecuadamente en cada caso. No es necesario ponerlos en un bucle.

gris
fuente
24
+1 Ese es un patrón muy extraño y probablemente se pueda eliminar.
m0skit0
3
Si t1 termina primero, luego t2 para terminar. Eso parece ser un proceso secuencial. Un hilo termina primero, luego el otro. ¿Cuál es el punto de múltiples hilos?
user697911
9
Porque t1y t2pueden ejecutarse en paralelo. Es solo que mainnecesita que ambos terminen antes de que pueda continuar. Ese es un patrón típico @ user697911.
Gris
3
¿El whilebucle está allí porque (supongo) que quiere volver a intentar las join()llamadas si se interrumpe una? Ciertamente no lo escribiría de esa manera @ user697911.
Gris
55
El bucle está ahí para asegurar que tanto t1y t2terminar. Es decir. si t1arroja el InterruptedException, retrocederá y esperará t2. Una alternativa es esperar ambos hilos en cada uno de sus Try-Catch, para evitar el bucle. Además, dependiendo de EventThread, puede tener sentido hacerlo de esta manera, ya que estamos ejecutando 2 hilos, no uno.
Michael Bisbjerg
68

Esta es una pregunta favorita de la entrevista 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()significa que t1 dice algo así como " Quiero terminar primero ". Igual es el caso con t2. No importa quién inició t1o t2subproceso (en este caso, el mainmétodo), main esperará hasta t1y t2finalizará su tarea.

Sin embargo, un punto importante a tener en cuenta, t1y t2ellos mismos pueden ejecutarse en paralelo, independientemente de la secuencia de llamada de combinación en t1y t2. Es el main/daemonhilo que tiene que esperar .

AmitG
fuente
3
Buen ejemplo. Acerca de "puede ejecutarse en paralelo": ¿y qué? Es importante que el hilo principal espere PRIMERO por t1 y LUEGO por t2. Realmente no importa lo que t1 o t2 estén haciendo (desde la perspectiva del hilo principal)
Alex
1
Usted menciona el hilo "main / daemon". Lo principal lo entiendo, pero el demonio no tiene nada que ver con eso. El hilo principal es no demonio.
Gris
1
t1.join(); t2.join();no permitirá que el hilo que ejecuta las uniones continúe hasta que ambos hilos hayan terminado. En ausencia de un código muy inusual en otra parte, el orden de las uniones no importa.
David Schwartz
Entonces, ¿se llamará a t2.join () solo cuando t1 haya terminado?
Leo Droidcoder
En otras palabras, si queremos "serializar" la ejecución de subprocesos t1 y t2, debemos colocar t1.join () justo después de t1.start () ya que el subproceso principal inició t1 (y t2 después) y, por supuesto, eliminarlo de trata de atraparlo. Obviamente, al hacerlo, la consecuencia será una pérdida de paralelismo.
dobrivoje
47

join()significa esperar a que se complete un hilo. Este es un método de bloqueo. Su hilo principal (el que hace el join()) esperará en la t1.join()línea hasta que t1termine su trabajo, y luego hará lo mismo t2.join().

Avi
fuente
29

Una imagen vale mas que mil palabras.

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

Espero sea útil, para más detalles haga clic aquí

xxy
fuente
3
Claro y preciso.
dobrivoje
10

Cuando el hilo tA llama a tB.join (), sus causas no solo esperan a que tB muera o tA se interrumpa, sino que crea una relación de suceso anterior entre la última declaración en tB y la siguiente declaración después de tB.join () en el hilo tA.

Todas las acciones en un subproceso suceden antes de que cualquier otro subproceso regrese con éxito desde un join () en ese subproceso.

Significa programa

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

Siempre imprimir

>> 1111111111111111111111111 ...

Pero programa

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

Puede imprimir no solo

>> 0000000000 ... 000000111111111111111111111111 ...

Pero

>> 00000000000000000000000000000000000000000000 ... 

Siempre solo '0'.

Debido a que Java Memory Model no requiere 'transferir' un nuevo valor de 'sharedVar' de threadB a thread principal sin una relación heppens-before (inicio de hilo, unión de hilo, uso de palabra clave 'sincronizada', uso de variables AtomicXXX, etc.).

Ivan Golovach
fuente
5

En pocas palabras:
t1.join()regresa después de que t1se complete.
No hace nada para enhebrar t1, excepto esperar a que termine.
Naturalmente, el siguiente código t1.join()se ejecutará solo después de las t1.join()devoluciones.

c0der
fuente
1
se ejecutará solo después de que t1.join () devuelva +1
mohsen.nour el
3

Desde la página de documentación de Oracle en Uniones

El joinmétodo permite que un hilo espere a que se complete otro.

Si t1 es un Threadobjeto cuyo hilo se está ejecutando actualmente,

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

Si t2 es un Threadobjeto cuyo hilo se está ejecutando actualmente,

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

joinAPI es API de bajo nivel, que se ha introducido en versiones anteriores de java. Muchas cosas han cambiado durante un período de tiempo (especialmente con la versión jdk 1.5) en el frente de concurrencia.

Puede lograr lo mismo con la API java.util.concurrent. Algunos de los ejemplos son

  1. Usando invokeAll enExecutorService
  2. Usando CountDownLatch
  3. Usando ForkJoinPool o newWorkStealingPool de Executors(desde java 8)

Consulte las preguntas relacionadas de SE:

esperar hasta que todos los hilos terminen su trabajo en java

Ravindra babu
fuente
1

Para mí, el comportamiento de Join () siempre fue confuso porque estaba tratando de recordar quién esperará a quién. No trates de recordarlo de esa manera.

Debajo, es puro mecanismo wait () y notify ().

Todos sabemos que, cuando llamamos a wait () en cualquier objeto (t1), el objeto que llama (main) se envía a la sala de espera (estado bloqueado).

Aquí, el hilo principal está llamando a join (), que es wait () debajo de las cubiertas. Entonces el hilo principal esperará hasta que sea notificado. La notificación es dada por t1 cuando termina su ejecución (finalización de subproceso).

Después de recibir la notificación, main sale de la sala de espera y procede a su ejecución.

Arjun Patil
fuente
0

¡Espero eso ayude!

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
}
Parthasarathy S
fuente
-3

Digamos que nuestro hilo principal comienza los hilos t1 y t2. Ahora, cuando se llama a t1.join (), el hilo principal se suspende hasta que el hilo t1 muere y luego se reanuda. De manera similar, cuando se ejecuta t2.join (), el hilo principal se suspende nuevamente hasta que el hilo t2 muere y luego se reanuda.

Entonces, así es como funciona.

Además, el ciclo while no era realmente necesario aquí.

skmangalam
fuente