Un escenario simple usando wait () y notify () en java

181

¿Puedo obtener un escenario simple y completo, es decir, un tutorial que sugiera cómo se debe usar esto, específicamente con una cola?

Olaseni
fuente

Respuestas:

269

Los métodos wait()y notify()están diseñados para proporcionar un mecanismo que permita que un subproceso se bloquee hasta que se cumpla una condición específica. Para esto, supongo que desea escribir una implementación de cola de bloqueo, donde tenga algún almacén de elementos de respaldo de tamaño fijo.

Lo primero que debe hacer es identificar las condiciones que desea que esperen los métodos. En este caso, deseará que el put()método se bloquee hasta que haya espacio libre en la tienda, y deseará que el take()método se bloquee hasta que haya algún elemento que devolver.

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void put(T element) throws InterruptedException {
        while(queue.size() == capacity) {
            wait();
        }

        queue.add(element);
        notify(); // notifyAll() for multiple producer/consumer threads
    }

    public synchronized T take() throws InterruptedException {
        while(queue.isEmpty()) {
            wait();
        }

        T item = queue.remove();
        notify(); // notifyAll() for multiple producer/consumer threads
        return item;
    }
}

Hay algunas cosas a tener en cuenta sobre la forma en que debe utilizar los mecanismos de espera y notificación.

En primer lugar, debe asegurarse de que cualquier llamada wait()ao notify()esté dentro de una región de código sincronizada (con las llamadas wait()y notify()sincronizadas en el mismo objeto). La razón de esto (aparte de las preocupaciones de seguridad de hilo estándar) se debe a algo conocido como una señal perdida.

Un ejemplo de esto es que un subproceso puede llamar put()cuando la cola está llena, luego verifica la condición, ve que la cola está llena, sin embargo, antes de que pueda bloquear otro subproceso está programado. Este segundo subproceso take()es un elemento de la cola y notifica a los subprocesos en espera que la cola ya no está llena. Sin embargo, debido a que el primer subproceso ya ha verificado la condición, simplemente llamará wait()después de ser reprogramado, a pesar de que podría avanzar.

Al sincronizar un objeto compartido, puede asegurarse de que este problema no ocurra, ya que la take()llamada del segundo subproceso no podrá avanzar hasta que el primer subproceso se haya bloqueado realmente.

En segundo lugar, debe colocar la condición que está verificando en un ciclo while, en lugar de una declaración if, debido a un problema conocido como despertadores espurios. Aquí es donde un hilo de espera a veces se puede reactivar sin notify()ser llamado. Al poner esta verificación en un bucle while, se garantizará que si se produce una activación espuria, la condición se volverá a verificar y el hilo volverá a llamar wait().


Como algunas de las otras respuestas han mencionado, Java 1.5 introdujo una nueva biblioteca de concurrencia (en el java.util.concurrentpaquete) que fue diseñada para proporcionar una abstracción de nivel superior sobre el mecanismo de espera / notificación. Con estas nuevas funciones, podría reescribir el ejemplo original de esta manera:

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public void put(T element) throws InterruptedException {
        lock.lock();
        try {
            while(queue.size() == capacity) {
                notFull.await();
            }

            queue.add(element);
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while(queue.isEmpty()) {
                notEmpty.await();
            }

            T item = queue.remove();
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

Por supuesto, si realmente necesita una cola de bloqueo, debe usar una implementación de la interfaz BlockingQueue .

Además, para cosas como esta, recomiendo Java Concurrency in Practice , ya que cubre todo lo que pueda desear saber sobre problemas y soluciones relacionados con la concurrencia.

Jared Russell
fuente
77
@greuze, notifysolo despierta un hilo. Si dos subprocesos del consumidor compiten para eliminar un elemento, una notificación puede despertar al otro subproceso del consumidor, que no puede hacer nada al respecto y volverá a dormir (en lugar del productor, que esperábamos insertar un nuevo elemento). el hilo productor no se despierta, no se inserta nada y ahora los tres hilos dormirán indefinidamente. Eliminé mi comentario anterior porque decía (erróneamente) que el despertar espurio era la causa del problema (no lo es)
Finnw
1
@finnw Por lo que puedo decir, el problema que has detectado se puede resolver mediante notifyAll (). Estoy en lo cierto?
Clint Eastwood
1
El ejemplo dado aquí por @Jared es bastante bueno pero tiene una caída grave. En el código, todos los métodos se han marcado como sincronizados, pero NO SE PUEDEN EJECUTAR DOS MÉTODOS SINCRONIZADOS AL MISMO TIEMPO, entonces, ¿cómo es que hay un segundo hilo en la imagen?
Shivam Aggarwal
10
@ Brut3Forc3 necesita leer el javadoc de wait (): dice: El hilo libera la propiedad de este monitor . Entonces, tan pronto como se llama a wait (), se libera el monitor y otro subproceso puede ejecutar otro método sincronizado de la cola.
JB Nizet
1
@JBNizet. "Un ejemplo de esto es que un subproceso puede llamar a put () cuando la cola está llena, luego verifica la condición, ve que la cola está llena, sin embargo, antes de que pueda bloquear otro subproceso está programado". el segundo hilo está programado si aún no se ha llamado a la espera.
Shivam Aggarwal
148

No es un ejemplo de cola, pero es extremadamente simple :)

class MyHouse {
    private boolean pizzaArrived = false;

    public void eatPizza(){
        synchronized(this){
            while(!pizzaArrived){
                wait();
            }
        }
        System.out.println("yumyum..");
    }

    public void pizzaGuy(){
        synchronized(this){
             this.pizzaArrived = true;
             notifyAll();
        }
    }
}

Algunos puntos importantes:
1) NUNCA

 if(!pizzaArrived){
     wait();
 }

Siempre use while (condición), porque

  • a) los hilos pueden despertarse esporádicamente del estado de espera sin ser notificados por nadie. (incluso cuando el pizzero no tocó el timbre, alguien decidiría intentar comer la pizza).
  • b) Debe verificar la condición nuevamente después de adquirir el bloqueo sincronizado. Digamos que la pizza no dura para siempre. Te despiertas, haces cola para la pizza, pero no es suficiente para todos. Si no revisas, ¡podrías comer papel! :) (probablemente sería un mejor ejemplo while(!pizzaExists){ wait(); }.

2) Debe mantener el bloqueo (sincronizado) antes de invocar wait / nofity. Los hilos también deben adquirir el bloqueo antes de despertarse.

3) Intente evitar adquirir cualquier bloqueo dentro de su bloque sincronizado y trate de no invocar métodos extraños (métodos que no sabe con certeza lo que están haciendo). Si es necesario, asegúrese de tomar medidas para evitar puntos muertos.

4) Tenga cuidado al notificar (). Quédese con notifyAll () hasta que sepa lo que está haciendo.

5) ¡Por último, pero no menos importante, lea la concurrencia de Java en la práctica !

Enno Shioji
fuente
1
¿Podría explicarnos por qué no usar "if (! PizzaArrived) {wait ();}"?
Todos los
2
@ Todos: Se agregó alguna explicación. HTH
Enno Shioji
1
¿Por qué usar la pizzaArrivedbandera? Si la bandera se cambia sin una llamada notify, no tendrá ningún efecto. También solo con waity notifyllama el ejemplo funciona.
Pablo Fernández
2
No entiendo: el hilo 1 ejecuta el método eatPizza () e ingresa el bloque superior sincronizado y se sincroniza en la clase MyHouse. Todavía no ha llegado la pizza, así que solo espera. Ahora el hilo 2 intenta entregar la pizza llamando al método pizzaGuy (); pero no puede porque el subproceso 1 ya posee el bloqueo y no lo está abandonando (está perpetuamente esperando). Efectivamente, el resultado es un punto muerto: el subproceso 1 está esperando que el subproceso 2 ejecute el método notifyAll (), mientras que el subproceso 2 espera que el subproceso 1 abandone el bloqueo en la clase MyHouse ... ¿Qué es lo que me falta? ¿aquí?
flamming_python
1
No, cuando una variable está protegida por una synchronizedpalabra clave, es redundante declarar la variable volatile, y se recomienda evitarla para evitar confusiones @mrida
Enno Shioji
37

Aunque lo solicitó wait()y notify()específicamente, creo que esta cita sigue siendo lo suficientemente importante:

Josh Bloch, Effective Java 2nd Edition , Item 69: Prefiere las utilidades de concurrencia waity notify(énfasis suyo):

Dada la dificultad de usarlo waity notifycorrectamente, debe usar las utilidades de concurrencia de nivel superior en lugar de usarlo [...] waity notifydirectamente es como programar en "lenguaje ensamblador de concurrencia", en comparación con el lenguaje de nivel superior proporcionado por java.util.concurrent. Rara vez, si alguna vez, hay razones para usar waity notifyen un nuevo código .

poligenelubricantes
fuente
Los BlockingQueueS proporcionados en el paquete java.util.concurrent no son persistentes. ¿Qué podemos usar cuando la cola debe ser persistente? es decir, si el sistema deja de funcionar con 20 elementos en la cola, necesito que estén presentes cuando el sistema se reinicie. Como las colas java.util.concurrent parecen estar 'en memoria', ¿hay alguna forma de que se puedan usar como / hackeadas / anuladas para proporcionar implementaciones que sean capaces de persistencia?
Volksman
1
¿Quizás se pueda proporcionar la cola de respaldo? es decir, proporcionaríamos una implementación de interfaz de cola que sea persistente.
Volksman
Es muy bueno mencionar en este contexto que nunca necesitarías usar el notify()y el wait()otro
Chaklader Asfak Arefe
7

¿Has echado un vistazo a este tutorial de Java ?

Además, te aconsejo que te mantengas alejado de jugar con este tipo de cosas en software real. Es bueno jugar con él para que sepas lo que es, pero la concurrencia tiene dificultades por todas partes. Es mejor utilizar abstracciones de nivel superior y colecciones sincronizadas o colas JMS si está creando software para otras personas.

Eso es al menos lo que hago. No soy un experto en concurrencia, así que me mantengo alejado del manejo de hilos a mano siempre que sea posible.

extraneon
fuente
2

Ejemplo

public class myThread extends Thread{
     @override
     public void run(){
        while(true){
           threadCondWait();// Circle waiting...
           //bla bla bla bla
        }
     }
     public synchronized void threadCondWait(){
        while(myCondition){
           wait();//Comminucate with notify()
        }
     }

}
public class myAnotherThread extends Thread{
     @override
     public void run(){
        //Bla Bla bla
        notify();//Trigger wait() Next Step
     }

}
Ferhat KOÇER
fuente
0

Ejemplo para wait () y notifyall () en Threading.

Se utiliza una lista de matriz estática sincronizada como recurso y se llama al método wait () si la lista de matriz está vacía. El método notify () se invoca una vez que se agrega un elemento para la lista de la matriz.

public class PrinterResource extends Thread{

//resource
public static List<String> arrayList = new ArrayList<String>();

public void addElement(String a){
    //System.out.println("Add element method "+this.getName());
    synchronized (arrayList) {
        arrayList.add(a);
        arrayList.notifyAll();
    }
}

public void removeElement(){
    //System.out.println("Remove element method  "+this.getName());
    synchronized (arrayList) {
        if(arrayList.size() == 0){
            try {
                arrayList.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }else{
            arrayList.remove(0);
        }
    }
}

public void run(){
    System.out.println("Thread name -- "+this.getName());
    if(!this.getName().equalsIgnoreCase("p4")){
        this.removeElement();
    }
    this.addElement("threads");

}

public static void main(String[] args) {
    PrinterResource p1 = new PrinterResource();
    p1.setName("p1");
    p1.start();

    PrinterResource p2 = new PrinterResource();
    p2.setName("p2");
    p2.start();


    PrinterResource p3 = new PrinterResource();
    p3.setName("p3");
    p3.start();


    PrinterResource p4 = new PrinterResource();
    p4.setName("p4");
    p4.start();     

    try{
        p1.join();
        p2.join();
        p3.join();
        p4.join();
    }catch(InterruptedException e){
        e.printStackTrace();
    }
    System.out.println("Final size of arraylist  "+arrayList.size());
   }
}
srinivas
fuente
1
por favor revise esta línea if(arrayList.size() == 0), creo que podría ser un error aquí.
Wizmann