Los Métodos notify() y wait()

Los métodos get() y put() del objeto CubbyHole hacen uso de los métodos notify() y wait() para coordinar la obtención y puesta de valores dentro de CubbyHole. Los métodos notify() y wait() son miembros de la clase java.lang.Object.


Nota: Los métodos notify() y wait() pueden ser invocados sólo desde dentro de un método sincronizado o dentro de un bloque o una sentencia sincronizada.

Investiguemos el uso del método notify() en CubbyHole mirando el método get().

El método notify()

El método get() llama al método notify() como lo último que hace (junto retornar). El método notify() elige un thread que está esperando el monitor poseido por el thread actual y lo despierta. Normalmente, el thread que espera capturará el monitor y procederá.

El caso del ejemplo Productor/Consumidor, el thread Consumidor llama al método get(), por lo que el método Consumidor posee el monitor de CubbyHole durante la ejecución del método get(). Al final del método get(), la llamada al método notify() despierta al thread Productor que obtiene el monitor de CubbyHole y procede.

public synchronized int get() {
    while (available == false) {
        try {
            wait();
        } catch (InterruptedException e) {
        }
    }
    available = false;
    notify();           // lo notifica al Productor
    return contents;
}
Si existen varios threads esperando por un monitor, el sistema de ejecución Java elige uno de esos threads, sin ningún compromiso ni garantía sobre el thread que será eligido.

El método put() trabaja de un forma similar a get(), despertanto al thread consumidor que está esperando que el Productor libere el monitor.

La clase Object tiene otro método --notifyAll()-- que despierta todos lo threads que están esperando al mismo monitor. En esta Situación, los threads despertados compiten por el monitor. Uno de ellos obtiene el monitor y los otros vuelven a esperar.

El método wait()

El método wait() hace que el thread actual espere (posiblemente para siempre) hasta que otro thread se lo notifique o a que cambie un condición. Se utiliza el método wait() en conjunción con el método notify() para coordinar la actividad de varios threads que utilizan los mismos recursos.

El método get() contiene una sentencia while que hace un bucle hasta que available se convierte en true. Si available es false -- el Productor todavía no ha producido un nuevo número y el consumidor debe esperar -- el método get() llama a wait().

El bucle while contiene la llamada a wait(). El método wait() espera indefinidamente hasta que llegue una notificación del thread Productor. Cuando el método put() llama a notify(), el Consumidor despierta del estado de espera y continúa con el bucle. Presumiblemente, el Productor ya ha generado un nuevo número y el método get() cae al final del bucle y procede. Si el Productor no ha generado un nuevo número, get() vuelve al principio del bucle y continua espeando hasta que el Productor genere un nuevo número y llame a notify().

public synchronized int get() {
    while (available == false) {
        try {
            wait();          // espera una llamada a notify() desde el Productor
        } catch (InterruptedException e) {
        }
    }
    available = false;
    notify();
    return contents;
}

El método put() trabaja de un forma similar, esperando a que el thread Consumidor consuma el valor actual antes de permitir que el Productor genere uno nuevo.

Junto a la versión utilizada en el ejemplo de Productor/Consumidor, que espera indefinidamente una notificación, la clase Object contiene otras dos versiones del método wait():

wait(long timeout)
Espera una notificación o hasta que haya pasado el tiempo de espera --timeout se mide en milisegundos.

wait(long timeout, int nanos)
Espera una notificación o hasta que hayan pasado timeout milisegundos mas nanos nanosegundos.

Los Monitores y los Métodos notify() y wait()

Habras observado un problema potencial en los métodos put() y get() de CubbyHole. Al principio del método get(), si el valor de CubbyHole no está disponible (esto es, el Productor no ha generado un nuevo número desde la última vez que el Consumidor lo consumió), luego el Consumidor espera a que el Productor ponga un nuevo número en CubbyHole. Aquí está la cuestión -- ¿cómo puede el Productor poner un nuevo valor dentro de CubbyHole si el Consumidor tiene el monitor? (El Consumidor posee el monitor de CubbyHole porque está dentro del método get() que está sincronizado).

Similarmente, al principio del método put(), si todavía no se ha consumido el valor, el Productor espera a que el Consumidor consuma el valor del CubbyHole. Y de nuevo llegamos a la cuestión -- ¿Cómo puede el consumidor obtener el valor de CubbyHole, si el Productor posee el monitor? (El productor posee el monitor de CubbyHole porque está dentro dentro del método put() que está sincronizado).

Bien, los diseñadores del lenguaje Java también pensaron en esto. Cuando el thread entra en el método wait(), lo que sucede al principio de los métodos put() y get, el monitor es liberado automáticamente, y cuando el thread sale del método wait(), se adquiere de nuevo el monitor. Esto le da una oportunidad al objeto que está esperando de adquirir el monitor y, dependiendo, de quién está esperando, consume el valor de CubbyHole o produce un nuevo valor para el CubbyHole.


Ozito