Un escenario simple usando esperar() y notificar() en java

Resuelto Olaseni asked hace 14 años • 7 respuestas

¿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 avatar Mar 29 '10 15:03 Olaseni
Aceptado

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 un almacén de respaldo de elementos de tamaño fijo.

Lo primero que debe hacer es identificar las condiciones por las que desea que esperen los métodos. En este caso, querrá que el put()método se bloquee hasta que haya espacio libre en la tienda, y querrá 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 todas las llamadas a wait()o notify()estén 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 estándar sobre la seguridad de los subprocesos) se debe a algo conocido como señal perdida.

Un ejemplo de esto es que un hilo 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 se programa otro hilo. Este segundo hilo take()es entonces un elemento de la cola y notifica a los hilos en espera que la cola ya no está llena. Sin embargo, debido a que el primer hilo ya verificó la condición, simplemente llamará wait()después de ser reprogramado, aunque podría progresar.

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

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


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

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, entonces debería utilizar una implementación de la interfaz BlockingQueue .

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

Jared Russell avatar Mar 29 '2010 10:03 Jared Russell

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 hagas

 if(!pizzaArrived){
     wait();
 }

Utilice siempre while (condición), porque

  • a) los subprocesos pueden despertar esporádicamente del estado de espera sin que nadie los notifique. (Incluso cuando el pizzero no tocaba el timbre, alguien decidía intentar comerse 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 fila para comer pizza, pero no es suficiente para todos. ¡Si no lo haces, podrías comer papel! :) (probablemente un mejor ejemplo sería while(!pizzaExists){ wait(); }.

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

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

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

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

Enno Shioji avatar Mar 29 '2010 09:03 Enno Shioji

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

Josh Bloch, Effective Java 2nd Edition , Ítem 69: Prefiere utilidades de concurrencia a waity notify(énfasis en él):

Dada la dificultad de usar waity notifycorrectamente, debe usar las utilidades de concurrencia de nivel superior en lugar [...] usar 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, o nunca, hay motivos para utilizar waity notifyen un código nuevo .

polygenelubricants avatar Mar 29 '2010 09:03 polygenelubricants

¿ 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 saber qué es, pero la concurrencia tiene inconvenientes en 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, por lo que evito manejar subprocesos manualmente siempre que sea posible.

extraneon avatar Mar 29 '2010 08:03 extraneon