Pregunta

Si uno busca en Google "diferencia entre notify() y notifyAll()" entonces aparecerán muchas explicaciones (dejando de lado los párrafos de javadoc).Todo se reduce a la cantidad de subprocesos en espera que se activan:uno en notify() y todo dentro notifyAll().

Sin embargo (si entiendo bien la diferencia entre estos métodos), siempre se selecciona solo un hilo para la adquisición adicional del monitor;en el primer caso el seleccionado por la VM, en el segundo caso el seleccionado por el programador de subprocesos del sistema.El programador no conoce los procedimientos de selección exactos para ambos (en el caso general).

cual es el útil diferencia entre notificar() y notificar a todos() ¿entonces?¿Me estoy perdiendo de algo?

¿Fue útil?

Solución

Sin embargo (si entiendo bien la diferencia entre estos métodos), siempre se selecciona solo un hilo para la adquisición adicional del monitor.

Eso no es correcto. o.notifyAll() despierta todo de los hilos que están bloqueados en o.wait() llamadas.Los hilos sólo pueden regresar desde o.wait() uno por uno, pero cada uno voluntad obtener su turno.


En pocas palabras, depende de por qué sus hilos están esperando ser notificados.¿Quieres contarle a uno de los hilos en espera que algo sucedió o quieres contarles a todos al mismo tiempo?

En algunos casos, todos los subprocesos en espera pueden realizar acciones útiles una vez que finaliza la espera.Un ejemplo sería un conjunto de subprocesos esperando a que finalice una determinada tarea;Una vez finalizada la tarea, todos los hilos en espera pueden continuar con su negocio.En tal caso usarías notificar a todos() para despertar todos los hilos en espera al mismo tiempo.

En otro caso, por ejemplo, bloqueo mutuamente excluyente, solo uno de los subprocesos en espera puede hacer algo útil después de recibir una notificación (en este caso, adquirir el bloqueo).En tal caso, preferiría utilizar notificar().Si se implementa correctamente, usted podría usar notificar a todos() en esta situación también, pero innecesariamente despertarías hilos que no pueden hacer nada de todos modos.


En muchos casos, el código para esperar una condición se escribirá como un bucle:

synchronized(o) {
    while (! IsConditionTrue()) {
        o.wait();
    }
    DoSomethingThatOnlyMakesSenseWhenConditionIsTrue_and_MaybeMakeConditionFalseAgain();
}

De esa manera, si un o.notifyAll() La llamada despierta más de un hilo en espera, y el primero en regresar del o.wait() make deja la condición en el estado falso, entonces los otros hilos que fueron despertados volverán a esperar.

Otros consejos

Claramente, notify despierta (cualquiera) un hilo en el conjunto de espera, notifyAll Despierta todos los hilos en el conjunto de espera.La siguiente discusión debería aclarar cualquier duda. notifyAll debe usarse la mayor parte del tiempo.Si no está seguro de cuál usar, utilice notifyAll.Consulte la explicación a continuación.

Lea con mucha atención y comprenda.Por favor envíeme un correo electrónico si tiene alguna pregunta.

Mire productor/consumidor (se supone que es una clase ProducerConsumer con dos métodos).ESTA ROTO (porque usa notify) - sí, PUEDE funcionar - incluso la mayor parte del tiempo, pero también puede causar un punto muerto - veremos por qué:

public synchronized void put(Object o) {
    while (buf.size()==MAX_SIZE) {
        wait(); // called if the buffer is full (try/catch removed for brevity)
    }
    buf.add(o);
    notify(); // called in case there are any getters or putters waiting
}

public synchronized Object get() {
    // Y: this is where C2 tries to acquire the lock (i.e. at the beginning of the method)
    while (buf.size()==0) {
        wait(); // called if the buffer is empty (try/catch removed for brevity)
        // X: this is where C1 tries to re-acquire the lock (see below)
    }
    Object o = buf.remove(0);
    notify(); // called if there are any getters or putters waiting
    return o;
}

EN PRIMER LUGAR,

¿Por qué necesitamos un bucle while que rodee la espera?

Necesitamos una while bucle en caso de que tengamos esta situación:

El consumidor 1 (C1) ingresa al bloque sincronizado y el búfer está vacío, por lo que C1 se coloca en el conjunto de espera (a través del wait llamar).El Consumidor 2 (C2) está a punto de ingresar al método sincronizado (en el punto Y anterior), pero el Productor P1 coloca un objeto en el búfer y posteriormente llama notify.El único hilo en espera es C1, por lo que se despierta y ahora intenta volver a adquirir el bloqueo del objeto en el punto X (arriba).

Ahora C1 y C2 están intentando adquirir el bloqueo de sincronización.Uno de ellos (de forma no determinista) es elegido e ingresa al método, el otro es bloqueado (no espera, sino bloqueado, tratando de adquirir el bloqueo del método).Digamos que C2 obtiene el bloqueo primero.C1 todavía está bloqueando (intentando adquirir el bloqueo en X).C2 completa el método y libera el bloqueo.Ahora, C1 adquiere el candado.Adivina qué, por suerte tenemos un while bucle, porque C1 realiza la verificación del bucle (guardia) y se le impide eliminar un elemento inexistente del búfer (¡C2 ya lo obtuvo!).Si no tuviéramos un while, obtendríamos un IndexArrayOutOfBoundsException ¡Mientras C1 intenta eliminar el primer elemento del búfer!

AHORA,

Bien, ahora ¿por qué necesitamos notificar a todos?

En el ejemplo anterior de productor/consumidor parece que podemos salirnos con la nuestra notify.Parece así, porque podemos probar que los guardias en el esperar Los bucles para productor y consumidor son mutuamente excluyentes.Es decir, parece que no podemos tener un hilo esperando en el put método así como el get método, porque, para que eso sea cierto, entonces lo siguiente tendría que ser cierto:

buf.size() == 0 AND buf.size() == MAX_SIZE (supongamos que MAX_SIZE no es 0)

SIN EMBARGO, esto no es suficiente, NECESITAMOS usar notifyAll.Veamos por qué...

Supongamos que tenemos un búfer de tamaño 1 (para que el ejemplo sea fácil de seguir).Los siguientes pasos nos llevan a un punto muerto.Tenga en cuenta que CUALQUIER MOMENTO que se activa un subproceso con notificación, la JVM puede seleccionarlo de forma no determinista; es decir, se puede activar cualquier subproceso en espera.También tenga en cuenta que cuando varios subprocesos bloquean la entrada a un método (es decir,intentando adquirir un candado), el orden de adquisición puede ser no determinista.Recuerde también que un hilo solo puede estar en uno de los métodos a la vez: los métodos sincronizados permiten que solo se ejecute un hilo (es decir,manteniendo el bloqueo de) cualquier método (sincronizado) en la clase.Si ocurre la siguiente secuencia de eventos, se produce un punto muerto:

PASO 1:
- P1 pone 1 carácter en el buffer

PASO 2:
- Intentos P2 put - comprueba el bucle de espera - ya es un carácter - espera

PASO 3:
- Intentos P3 put - comprueba el bucle de espera - ya es un carácter - espera

ETAPA 4:
- C1 intenta obtener 1 carácter.
- C2 intenta obtener 1 bloque de carácter en la entrada al get método
- C3 intenta obtener 1 bloque de carácter en la entrada al get método

PASO 5:
- C1 está ejecutando el get método: obtiene el carácter, llama notify, método de salida
- El notify despierta P2
- PERO, C2 ingresa al método antes de que P2 pueda (P2 debe volver a adquirir el bloqueo), por lo que P2 bloquea la entrada al put método
- C2 comprueba el bucle de espera, no hay más caracteres en el buffer, por lo que espera
- C3 ingresa al método después de C2, pero antes de P2, verifica el bucle de espera, no hay más caracteres en el búfer, por lo que espera

PASO 6:
- AHORA:¡Hay P3, C2 y C3 esperando!
- Finalmente P2 adquiere el bloqueo, coloca un carácter en el búfer, llama a notificar y sale del método.

PASO 7:
- La notificación de P2 activa a P3 (recuerde que cualquier hilo puede activarse)
- P3 comprueba la condición del bucle de espera, ya hay un carácter en el búfer, por lo que espera.
- ¡NO MÁS HILOS PARA LLAMAR A NOTIFICAR y TRES HILOS SUSPENDIDOS PERMANENTEMENTE!

SOLUCIÓN:Reemplazar notify con notifyAll en el código de productor/consumidor (arriba).

Diferencias útiles:

  • Usar notificar() si todos sus hilos en espera son intercambiables (el orden en que se activan no importa), o si solo tiene un hilo en espera.Un ejemplo común es un grupo de subprocesos utilizado para ejecutar trabajos desde una cola: cuando se agrega un trabajo, se notifica a uno de los subprocesos que se active, ejecute el siguiente trabajo y vuelva a dormir.

  • Usar notificar a todos() para otros casos en los que los subprocesos en espera pueden tener diferentes propósitos y deberían poder ejecutarse simultáneamente.Un ejemplo es una operación de mantenimiento en un recurso compartido, donde varios subprocesos esperan a que se complete la operación antes de acceder al recurso.

Creo que depende de cómo se producen y consumen los recursos.Si hay 5 objetos de trabajo disponibles a la vez y tiene 5 objetos de consumo, tendría sentido activar todos los subprocesos usando notifyAll() para que cada uno pueda procesar 1 objeto de trabajo.

Si solo tiene un objeto de trabajo disponible, ¿qué sentido tiene despertar a todos los objetos de consumo para que compitan por ese objeto?El primero que busque trabajo disponible lo obtendrá y todos los demás hilos lo comprobarán y descubrirán que no tienen nada que hacer.

Encontre un gran explicación aquí.En breve:

El método notify () generalmente se usa para grupos de recursos, donde hay un número arbitrario de "consumidores" o "trabajadores" que toman recursos, pero cuando se agrega un recurso al grupo, solo uno de los consumidores o trabajadores que esperan pueden lidiar con ello.El método NotifyAll () en realidad se usa en la mayoría de los otros casos.Estrictamente, se requiere notificar a los camareros una condición que pueda permitir que múltiples camareros continúen.Pero esto a menudo es difícil de saber.Entonces como regla general, Si no tiene una lógica en particular para usar notificar (), entonces probablemente debería usar notifyall (), porque a menudo es difícil saber exactamente qué hilos esperarán en un objeto en particular y por qué.

Tenga en cuenta que con las utilidades de concurrencia también puede elegir entre signal() y signalAll() como se llaman estos métodos allí.Entonces la pregunta sigue siendo válida incluso con java.util.concurrent.

Doug Lea plantea un punto interesante en su libro famoso:si un notify() y Thread.interrupt() sucede al mismo tiempo, la notificación podría perderse.Si esto puede suceder y tiene implicaciones dramáticas notifyAll() es una opción más segura aunque pague el precio de los gastos generales (activar demasiados subprocesos la mayor parte del tiempo).

De Joshua Bloch, el propio Java Guru en Effective Java 2da edición:

"Artículo 69:Prefiero que las utilidades de concurrencia esperen y notifiquen".

Aquí hay un ejemplo.Ejecutarlo.Luego cambie uno de notifyAll() a notify() y vea qué sucede.

Clase ProducerConsumerExample

public class ProducerConsumerExample {

    private static boolean Even = true;
    private static boolean Odd = false;

    public static void main(String[] args) {
        Dropbox dropbox = new Dropbox();
        (new Thread(new Consumer(Even, dropbox))).start();
        (new Thread(new Consumer(Odd, dropbox))).start();
        (new Thread(new Producer(dropbox))).start();
    }
}

clase de dropbox

public class Dropbox {

    private int number;
    private boolean empty = true;
    private boolean evenNumber = false;

    public synchronized int take(final boolean even) {
        while (empty || evenNumber != even) {
            try {
                System.out.format("%s is waiting ... %n", even ? "Even" : "Odd");
                wait();
            } catch (InterruptedException e) { }
        }
        System.out.format("%s took %d.%n", even ? "Even" : "Odd", number);
        empty = true;
        notifyAll();

        return number;
    }

    public synchronized void put(int number) {
        while (!empty) {
            try {
                System.out.println("Producer is waiting ...");
                wait();
            } catch (InterruptedException e) { }
        }
        this.number = number;
        evenNumber = number % 2 == 0;
        System.out.format("Producer put %d.%n", number);
        empty = false;
        notifyAll();
    }
}

clase de consumidor

import java.util.Random;

public class Consumer implements Runnable {

    private final Dropbox dropbox;
    private final boolean even;

    public Consumer(boolean even, Dropbox dropbox) {
        this.even = even;
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            dropbox.take(even);
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) { }
        }
    }
}

clase de productor

import java.util.Random;

public class Producer implements Runnable {

    private Dropbox dropbox;

    public Producer(Dropbox dropbox) {
        this.dropbox = dropbox;
    }

    public void run() {
        Random random = new Random();
        while (true) {
            int number = random.nextInt(10);
            try {
                Thread.sleep(random.nextInt(100));
                dropbox.put(number);
            } catch (InterruptedException e) { }
        }
    }
}

Breve resumen:

Prefiero siempre notificar a todos() encima notificar() a menos que tenga una aplicación masivamente paralela donde una gran cantidad de subprocesos hacen lo mismo.

Explicación:

notificar() ...] despierta un solo hilo.Porque notificar() No le permite especificar el hilo que se despierta, es útil solo en aplicaciones masivamente paralelas, es decir, programas con una gran cantidad de hilos, todos haciendo tareas similares.En una aplicación de este tipo, no le importa qué hilo se activa.

fuente: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Comparar notificar() con notificar a todos() en la situación descrita anteriormente:una aplicación masivamente paralela donde los subprocesos hacen lo mismo.si llamas notificar a todos() en ese caso, notificar a todos() inducirá el despertar (es decir,programación) de una gran cantidad de subprocesos, muchos de ellos innecesariamente (ya que en realidad sólo un subproceso puede continuar, es decir, el subproceso al que se le otorgará el monitor para el objeto esperar(), notificar(), o notificar a todos() fue requerido), por lo tanto desperdiciando recursos informáticos.

Por lo tanto, si no tiene una aplicación en la que una gran cantidad de subprocesos hagan lo mismo al mismo tiempo, prefiera notificar a todos() encima notificar().¿Por qué?Porque, como ya han respondido otros usuarios en este foro, notificar()

despierta un único hilo que está esperando en el monitor de este objeto....] La elección es arbitrario y ocurre a discreción de la implementación.

fuente:API de Java SE8 (https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)

Imagine que tiene una aplicación de consumidor productor donde los consumidores están listos (es decir, esperar() ing) para consumir, los productores están listos (es decir, esperar() ing) a producir y la cola de artículos (a producir/consumir) está vacía.En ese caso, notificar() podría despertar sólo a los consumidores y nunca a los productores porque la elección de quién se despierta es arbitrario.El ciclo productor-consumidor no progresaría aunque los productores y consumidores estén dispuestos a producir y consumir, respectivamente.En cambio, se despierta al consumidor (es decir,dejándo el esperar() estado), no saca un elemento de la cola porque está vacío, y notificar() Es otro consumidor para proceder.

A diferencia de, notificar a todos() despierta tanto a productores como a consumidores.La elección de quién está programado depende del planificador.Por supuesto, dependiendo de la implementación del programador, el programador también podría programar solo consumidores (p. ej.si asigna a los subprocesos de los consumidores una prioridad muy alta).Sin embargo, la suposición aquí es que el peligro de que el programador programe sólo a los consumidores es menor que el peligro de que la JVM sólo despierte a los consumidores porque cualquier programador razonablemente implementado no realiza arbitrario decisiones.Más bien, la mayoría de las implementaciones de programadores hacen al menos algún esfuerzo para evitar la inanición.

Estoy muy sorprendido de que nadie haya mencionado el infame problema del "despertador perdido" (búsquelo en Google).

Básicamente:

  1. si tiene varios hilos esperando en la misma condición y,
  2. múltiples hilos que pueden hacerle pasar del estado A al estado B y,
  3. múltiples subprocesos que pueden hacerle pasar del estado B al estado A (generalmente los mismos subprocesos que en 1.) y,
  4. la transición del estado A al B debería notificar a los subprocesos en 1.

ENTONCES deberías usar notifyAll a menos que tengas garantías comprobables de que los despertares perdidos son imposibles.

Un ejemplo común es una cola FIFO concurrente donde:múltiples puestos de cola (1.y 3.Arriba) puede hacer la transición de su cola de múltiples dequeupers vacíos a múltiples no vacíos (2.arriba) puede esperar la condición "la cola no está vacía" vacía -> no vacío debe notificar a los dequeadores

Puede escribir fácilmente un entrelazado de operaciones en el que, a partir de una cola vacía, interactúen 2 puestos en cola y 2 quitadores de cola y 1 puesto en cola permanecerá inactivo.

Podría decirse que se trata de un problema comparable al problema del punto muerto.

Espero que esto aclare algunas dudas.

notificar() :El método Notify () despierta un hilo esperando el bloqueo (el primer hilo que se llama Wait () en ese bloqueo).

notificar a todos() :El método notifyAll() activa todos los subprocesos que esperan el bloqueo;El JVM selecciona uno de los hilos de la lista de hilos que esperan el bloqueo y se desplaza que se eleva.

En el caso de un solo hilo Mientras espera un bloqueo, no existe una diferencia significativa entre notificar () y notificar a todos ().Sin embargo, cuando hay más de un subproceso esperando el bloqueo, tanto en notify() como en notifyAll(), el subproceso exacto que se despierta es bajo el control de la JVM y no puedes controlar mediante programación la activación de un hilo específico.

A primera vista, parece que es una buena idea simplemente llamar a notify() para activar un hilo;Puede parecer innecesario activar todos los hilos.Sin embargo, el problema con notify() es que el hilo activado podría no ser el adecuado para ser despertado (el hilo puede estar esperando alguna otra condición, o la condición aún no se cumple para ese hilo, etc.). En ese caso, notify() podría perderse y ningún otro subproceso se activará, lo que podría provocar un tipo de punto muerto (la notificación se pierde y todos los demás subprocesos están esperando la notificación, para siempre).

Para evitar este problema, siempre es mejor llamar a notifyAll() cuando hay más de un hilo esperando un bloqueo (o más de una condición en la que se realiza la espera).El método notifyAll() activa todos los subprocesos, por lo que no es muy eficiente.sin embargo, esta pérdida de rendimiento es insignificante en aplicaciones del mundo real.

notify() despertará un hilo mientras notifyAll() despertará a todos.Hasta donde yo sé, no hay término medio.Pero si no estás seguro de qué notify() lo hará con tus hilos, usa notifyAll().Funciona a las mil maravillas en todo momento.

Todas las respuestas anteriores son correctas, hasta donde yo sé, así que te diré algo más.Para el código de producción, realmente deberías usar las clases en java.util.concurrent.Hay muy pocas cosas que no puedan hacer por usted en el área de concurrencia en Java.

Aquí hay una explicación más simple:

Tiene razón en que ya sea que use notify() o notifyAll(), el resultado inmediato es que exactamente otro subproceso adquirirá el monitor y comenzará a ejecutarse.(Suponiendo que algunos subprocesos fueron bloqueados en espera() para este objeto, otros subprocesos no relacionados no están absorbiendo todos los núcleos disponibles, etc.) El impacto viene más tarde.

Supongamos que los subprocesos A, B y C estaban esperando este objeto y el subproceso A obtiene el monitor.La diferencia radica en lo que sucede una vez que A suelta el monitor.Si usó notificar(), entonces B y C todavía están bloqueados en espera():no están esperando en el monitor, están esperando ser notificados.Cuando A suelta el monitor, B y C seguirán sentados allí, esperando una notificación().

Si utilizó notifyAll(), entonces B y C han avanzado más allá del estado "esperar notificación" y ambos están esperando adquirir el monitor.Cuando A libera el monitor, B o C lo adquirirán (suponiendo que no haya otros subprocesos compitiendo por ese monitor) y comenzarán a ejecutarse.

Hay tres estados para un hilo.

  1. ESPERA: el subproceso no está utilizando ningún ciclo de CPU
  2. BLOQUEADO: el hilo está bloqueado al intentar adquirir un monitor. Es posible que todavía esté usando los ciclos de la CPU.
  3. EN EJECUCIÓN: el hilo se está ejecutando.

Ahora, cuando se llama a notify(), la JVM elige un subproceso y lo mueve al estado BLOQUEADO y, por lo tanto, al estado EN EJECUCIÓN, ya que no hay competencia por el objeto monitor.

Cuando se llama a notifyAll(), JVM selecciona todos los subprocesos y los mueve todos al estado BLOQUEADO.Todos estos subprocesos obtendrán el bloqueo del objeto de forma prioritaria. El subproceso que pueda adquirir el monitor primero podrá pasar al estado EN EJECUCIÓN y así sucesivamente.

Esta respuesta es una reescritura gráfica y una simplificación de la excelente respuesta de xagig, incluidos comentarios de eran.

¿Por qué utilizar notifyAll, incluso cuando cada producto está destinado a un único consumidor?

Consideremos a los productores y consumidores, simplificados de la siguiente manera.

Productor:

while (!empty) {
   wait() // on full
}
put()
notify()

Consumidor:

while (empty) {
   wait() // on empty
}
take()
notify()

Supongamos que hay 2 productores y 2 consumidores que comparten un buffer de tamaño 1.La siguiente imagen muestra un escenario que conduce a una punto muerto, lo que se evitaría si se usaran todos los hilos notificar a todos.

Cada notificación está etiquetada con el hilo que se activa.

deadlock due to notify

notify() le permite escribir código más eficiente que notifyAll().

Considere el siguiente fragmento de código que se ejecuta desde varios subprocesos paralelos:

synchronized(this) {
    while(busy) // a loop is necessary here
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notifyAll();
}

Se puede hacer más eficiente utilizando notify():

synchronized(this) {
    if(busy)   // replaced the loop with a condition which is evaluated only once
        wait();
    busy = true;
}
...
synchronized(this) {
    busy = false;
    notify();
}

En el caso de que tenga una gran cantidad de subprocesos o si la condición del bucle de espera es costosa de evaluar, notify() será significativamente más rápido que notifyAll().Por ejemplo, si tiene 1000 subprocesos, se activarán y evaluarán 999 subprocesos después de la primera notifyAll(), luego 998, luego 997, y así sucesivamente.Por el contrario, con la notify() solución, sólo se despertará un hilo.

Usar notifyAll() cuando necesitas elegir qué hilo hará el trabajo a continuación:

synchronized(this) {
    while(idx != last+1)  // wait until it's my turn
        wait();
}
...
synchronized(this) {
    last = idx;
    notifyAll();
}

Finalmente, es importante entender que en caso de notifyAll(), el código dentro synchronized Los bloques que se han despertado se ejecutarán secuencialmente, no todos a la vez.Digamos que hay tres subprocesos esperando en el ejemplo anterior y el cuarto subproceso llama notifyAll().Se despertarán los tres subprocesos, pero solo uno comenzará la ejecución y verificará la condición del while bucle.Si la condición es true, llamará wait() nuevamente, y solo entonces el segundo hilo comenzará a ejecutarse y verificará su while condición de bucle, etc.

Tomado de Blog sobre Java efectivo:

The notifyAll method should generally be used in preference to notify. 

If notify is used, great care must be taken to ensure liveness.

Entonces, lo que entiendo es (del blog antes mencionado, comentario de "Yann TM" en respuesta aceptada y Java documentos):

  • notificar():JVM despierta uno de los subprocesos en espera en este objeto.La selección del hilo se realiza de forma arbitraria y sin equidad.Así el mismo hilo puede despertarse una y otra vez.Entonces el estado del sistema cambia pero no se logra ningún progreso real.Creando así un bloqueo vivo.
  • notificar a todos() :JVM despierta todos los subprocesos y luego todos los subprocesos corren hacia el bloqueo de este objeto.Ahora, el programador de la CPU selecciona un subproceso que adquiere el bloqueo en este objeto.Este proceso de selección sería mucho mejor que la selección realizada por JVM.Garantizando así vivacidad.

Eche un vistazo al código publicado por @xagyg.

Supongamos que dos subprocesos diferentes están esperando dos condiciones diferentes:
El primer hilo Esta esperando por buf.size() != MAX_SIZE, y el segundo hilo Esta esperando por buf.size() != 0.

Supongamos que en algún momento buf.size() no es igual a 0.llamadas JVM notify() en lugar de notifyAll(), y se notifica al primer hilo (no al segundo).

El primer hilo se activa, comprueba si buf.size() que podría regresar MAX_SIZE, y vuelve a esperar.El segundo hilo no se despierta, continúa esperando y no llama. get().

notify() despierta el primer hilo que llamó wait() sobre el mismo objeto.

notifyAll() despierta todos los hilos que llamaron wait() sobre el mismo objeto.

El hilo de mayor prioridad se ejecutará primero.

Me gustaría mencionar lo que se explica en Java Concurrency in Practice:

Primer punto, ¿Notificar o Notificar a todos?

It will be NotifyAll, and reason is that it will save from signall hijacking.

Si dos hilos A y B esperan en diferentes condiciones de condición de la misma cola de condición y se llama a notificar, entonces es hasta JVM a qué hilo JVM notificará.

Ahora, si la notificación estaba destinada al hilo A y JVM notificado al hilo B, entonces el hilo B se despertará y verá que esta notificación no es útil, por lo que esperará nuevamente.Y Thread A nunca llegará a saber sobre esta señal perdida y alguien secuestró su notificación.

Por lo tanto, llamar a Notifyall resolverá este problema, pero nuevamente tendrá un impacto en el rendimiento, ya que notificará todos los subprocesos y todos los subprocesos competirán por el mismo bloqueo y implicará un interruptor de contexto y, por lo tanto, se cargará en CPU.Pero deberíamos preocuparnos por el rendimiento solo si se comporta correctamente, si su comportamiento en sí no es correcto, el rendimiento no sirve de nada.

Este problema se puede resolver utilizando el objeto Condición de bloqueo explícito Lock, proporcionado en jdk 5, ya que proporciona una espera diferente para cada predicado de condición.Aquí se comportará correctamente y no habrá problemas de rendimiento ya que llamará a la señal y se asegurará de que solo un hilo esté esperando esa condición.

notificar notificará solo a un subproceso que está en estado de espera, mientras que notificar a todos notificará a todos los subprocesos en estado de espera ahora todos los subprocesos notificados y todos los subprocesos bloqueados son elegibles para el bloqueo, de los cuales solo uno obtendrá el bloqueo y todos los demás (incluidos aquellos que antes estaban en estado de espera) estarán en estado bloqueado.

notify() - Selecciona un hilo aleatorio del conjunto de espera del objeto y lo coloca en el BLOCKED estado.El resto de los hilos en el conjunto de espera del objeto todavía están en el WAITING estado.

notifyAll() - Mueve todos los hilos del conjunto de espera del objeto a BLOCKED estado.Después de usar notifyAll(), no quedan subprocesos en el conjunto de espera del objeto compartido porque ahora todos están en BLOCKED estado y no en WAITING estado.

BLOCKED - bloqueado para la adquisición de cerraduras.WAITING - esperando notificación (o bloqueado para completar la unión).

Para resumir las excelentes explicaciones detalladas anteriores, y de la forma más sencilla que se me ocurre, esto se debe a las limitaciones del monitor integrado JVM, que 1) se adquiere en toda la unidad de sincronización (bloque u objeto) y 2) no discrimina sobre la condición específica sobre la que se espera/notifica.

Esto significa que si varios subprocesos están esperando en diferentes condiciones y se utiliza notify(), el subproceso seleccionado puede no ser el que progresaría en la nueva condición cumplida, lo que provocaría que ese subproceso (y otros subprocesos actualmente en espera que podrían cumplir la condición, etc.) no poder progresar y, finalmente, morir de hambre o colgar el programa.

Por el contrario, notifyAll() permite que todos los subprocesos en espera vuelvan a adquirir el bloqueo y verifiquen su condición respectiva, lo que eventualmente permitirá que se realicen progresos.

Por lo tanto, notify() se puede utilizar de forma segura sólo si se garantiza que cualquier subproceso en espera permitirá que se realice progreso en caso de ser seleccionado, lo que en general se cumple cuando todos los subprocesos dentro del mismo monitor verifican solo una y la misma condición, algo bastante raro. caso en aplicaciones del mundo real.

Cuando llamas a la espera () del "objeto" (esperando que se adquiera el bloqueo del objeto), esto liberará el bloqueo de ese objeto y ayudará a los otros subprocesos a bloquear este "objeto", en este escenario habrá más de 1 subproceso esperando el "recurso/objeto" (teniendo en cuenta que los otros subprocesos también emitieron la espera en el mismo objeto anterior y en el futuro habrá un subproceso que llenará el recurso/objeto e invocará notificar/notificar a todos).

Aquí, cuando emite la notificación del mismo objeto (desde el mismo/otro lado del proceso/código), esto liberará un único subproceso bloqueado y en espera (no todos los subprocesos en espera; este subproceso liberado será seleccionado por JVM Thread). El programador y todo el proceso de obtención de bloqueos en el objeto son los mismos que los habituales).

Si solo tiene un hilo que compartirá/trabajará en este objeto, está bien usar el método notify() solo en su implementación de notificación de espera.

Si se encuentra en una situación en la que más de un hilo lee y escribe en recursos/objetos según su lógica de negocios, entonces debe optar por notificarTodos()

ahora estoy viendo cómo exactamente la jvm identifica y rompe el hilo en espera cuando emitimos notify() en un objeto...

Si bien hay algunas respuestas sólidas arriba, me sorprende la cantidad de confusiones y malentendidos que he leído.Esto probablemente prueba la idea de que uno debería usar java.util.concurrent tanto como sea posible en lugar de intentar escribir su propio código concurrente roto.Volviendo a la pregunta:En resumen, la mejor práctica hoy en día es EVITAR notificar() en TODAS las situaciones debido al problema de activación perdida.Cualquiera que no entienda esto no debería poder escribir código de concurrencia de misión crítica.Si le preocupa el problema del pastoreo, una forma segura de lograr despertar un hilo a la vez es:1.Cree una cola de espera explícita para los subprocesos en espera;2.Haga que cada uno de los subprocesos en la cola espere a su predecesor;3.Haga que cada hilo llame a notifyAll() cuando haya terminado.O puede usar Java.util.concurrent.*, que ya lo ha implementado.

Despertar a todos no tiene mucha importancia aquí.espere, notifique y notifique a todos, todos estos se colocan después de poseer el monitor del objeto.Si un hilo está en la etapa de espera y se llama a notificar, este hilo tomará el bloqueo y ningún otro hilo en ese punto podrá tomar ese bloqueo.Por lo tanto, el acceso simultáneo no puede realizarse en absoluto.Hasta donde yo sé, cualquier llamada para esperar, notificar y notificar a todos se puede realizar solo después de bloquear el objeto.Corrígeme si estoy equivocado.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top