2011-08-30 21 views
9

Dado el siguiente código Java:Java wait()/join(): ¿Por qué esto no es un punto muerto?

public class Test { 

    static private class MyThread extends Thread { 
     private boolean mustShutdown = false; 

     @Override 
     public synchronized void run() { 
      // loop and do nothing, just wait until we must shut down 
      while (!mustShutdown) { 
       try { 
        wait(); 
       } catch (InterruptedException e) { 
        System.out.println("Exception on wait()"); 
       } 
      } 
     } 

     public synchronized void shutdown() throws InterruptedException { 
      // set flag for termination, notify the thread and wait for it to die 
      mustShutdown = true; 
      notify(); 
      join(); // lock still being held here, due to 'synchronized' 
     } 
    } 

    public static void main(String[] args) { 
     MyThread mt = new MyThread(); 
     mt.start(); 

     try { 
      Thread.sleep(1000); 
      mt.shutdown(); 
     } catch (InterruptedException e) { 
      System.out.println("Exception in main()"); 
     } 
    } 
} 

La ejecución de este esperará durante un segundo y luego salir correctamente. Pero eso es inesperado para mí, espero que ocurra un bloqueo de seguridad aquí.

Mi razonamiento es el siguiente: MyThread recién creado ejecutará run(), que se declara como 'sincronizado', por lo que puede llamar a wait() y leer con seguridad 'mustShutdown'; durante esa llamada a wait(), el bloqueo se libera y se vuelve a adquirir al regresar, como se describe en la documentación de wait(). Después de un segundo, el subproceso principal ejecuta shutdown(), que se sincroniza de nuevo para no tener acceso a mustShutdown al mismo tiempo que lo está leyendo el otro subproceso. A continuación, activa el otro hilo mediante notify() y espera a que se complete mediante join().

Pero, en mi opinión, no hay forma de que el otro subproceso vuelva de wait(), ya que necesita volver a adquirir el bloqueo en el objeto de subproceso antes de volver. No puede hacerlo porque shutdown() aún mantiene el bloqueo mientras está dentro de join(). ¿Por qué todavía funciona y sale correctamente?

+0

Su debido a los efectos secundarios de este tipo de rosca que se extiende directamente está mal visto. Debes implementar un Runnable que envuelves con un Thread. –

Respuesta

7

join() método llama internamente wait() que dará lugar a la liberación de la cerradura (del objeto Thread).

ver el código de join() a continuación:

public final synchronized void join(long millis) 
    throws InterruptedException { 
    .... 
    if (millis == 0) { 
     while (isAlive()) { 
     wait(0); //ends up releasing lock 
     } 
    } 
    .... 
} 

Por lo que su código lo ve y no se ve en general:: La razón por la cual su código de ver esto y no no se observa, en general, es porque las esperas método join()() en el objeto Tema en sí y, por consiguiente renuncia a bloqueo en el propio objeto de rosca y como método de su ejecución() también sincroniza en el mismo objeto de rosca, que se ve este escenario de otro modo inesperado.

+1

Eso de hecho lo explica. Pero, como señala Paŭlo Ebermann, la documentación no dice nada al respecto. ¿Puedo confiar en join() liberando el bloqueo? – jlh

+0

Absolutamente sí. El código no ha cambiado desde que llegué a la pubertad. No te preocupes –

+0

¡Muy bien! Muy apreciado, gracias! – jlh

1

La implementación de Thread.join usa wait, que libera su bloqueo, por lo que no impide que el otro hilo adquiera el bloqueo.

Aquí es una descripción paso a paso de lo que ocurre en este ejemplo:

Inicio del hilo MyThread en las principales método da como resultado un nuevo hilo ejecutar el método MyThread plazo. El subproceso principal duerme durante un segundo, lo que le da al nuevo subproceso suficiente tiempo para iniciar y adquirir el bloqueo en el objeto MyThread.

El nuevo hilo puede ingresar al método de espera y liberar su bloqueo. En este punto, el nuevo hilo permanece inactivo, no intentará volver a adquirir el bloqueo hasta que se lo despierte. El hilo no devuelve del método de espera todavía.

En este punto, el hilo principal se activa al apagar y apagar las llamadas en el objeto MyThread. No tiene ningún problema para adquirir el bloqueo porque el nuevo hilo lo liberó una vez que comenzó a esperar. Las llamadas al hilo principal notifican ahora. Al ingresar el método de unión, el hilo principal comprueba que el nuevo hilo todavía está vivo y luego espera, liberando el bloqueo.

La notificación se produce una vez que el hilo principal libera el bloqueo. Como el nuevo hilo estaba en el conjunto de espera para el bloqueo en el momento en que el hilo principal se llama notify, el nuevo hilo recibe la notificación y se activa. Puede adquirir el bloqueo, abandonar el método de espera y finalizar la ejecución del método de ejecución, liberando finalmente el bloqueo.

La terminación del nuevo subproceso hace que todos los subprocesos que esperan en su bloqueo reciban una notificación. Esto despierta el hilo principal, puede adquirir el bloqueo y comprobar que el nuevo hilo está muerto, luego saldrá del método de unión y finalizará la ejecución.

/** 
* Waits at most <code>millis</code> milliseconds for this thread to 
* die. A timeout of <code>0</code> means to wait forever. 
* 
* @param  millis the time to wait in milliseconds. 
* @exception InterruptedException if any thread has interrupted 
*    the current thread. The <i>interrupted status</i> of the 
*    current thread is cleared when this exception is thrown. 
*/ 
public final synchronized void join(long millis) 
throws InterruptedException { 
long base = System.currentTimeMillis(); 
long now = 0; 

if (millis < 0) { 
     throw new IllegalArgumentException("timeout value is negative"); 
} 

if (millis == 0) { 
    while (isAlive()) { 
    wait(0); 
    } 
} else { 
    while (isAlive()) { 
    long delay = millis - now; 
    if (delay <= 0) { 
     break; 
    } 
    wait(delay); 
    now = System.currentTimeMillis() - base; 
    } 
} 
} 
0

Para complementar las otras respuestas: veo ninguna mención de join() liberar los bloqueos en la API-documentación, por lo que este comportamiento es en realidad específica de la implementación.

aprender de esto:

  • no subclase Thread, en lugar de utilizar una aplicación Runnable pasado a su objeto hilo.
  • no se sincronizan/wait/notificar sobre los objetos no "posee", por ejemplo, donde no se sabe quién más podría sincronizar/esperar/notificar en él.
+0

Ejecutable en lugar de hilo de subclases parece más complicado para mí en esta situación específica ... Y sólo MyThread utiliza nunca 'sincronizados', wait(), join() y notify(). La clase principal nunca lo usa. (Aunque el hilo principal sí lo hace). Por lo tanto, no estoy seguro acerca de su segundo punto. – jlh

+0

El punto es que 'join' internamente usa' wait() ', lo cual no es de esperar. Es decir. el monitor del objeto 'MyThread' se usa con dos propósitos: sincronizar su propio ciclo de ejecución/apagado y sincronizar la administración interna de subprocesos. –