new - paneles java swing
Java: notificar() frente a notificarAll() de nuevo (26)
Sin embargo (si entiendo bien la diferencia entre estos métodos), solo se selecciona un hilo para la adquisición de monitores adicionales.
Eso no es correcto. o.notifyAll()
despierta todos los subprocesos que están bloqueados en las llamadas o.wait()
. Los hilos solo pueden regresar de o.wait()
uno por uno, pero cada uno recibirá su turno.
En pocas palabras, depende de por qué sus hilos están esperando para ser notificados. ¿Quiere decirle a uno de los hilos en espera que algo sucedió, o quiere contarlos a todos al mismo tiempo?
En algunos casos, todos los subprocesos en espera pueden realizar una acción útil una vez que finaliza la espera. Un ejemplo sería un conjunto de subprocesos a la espera de que una determinada tarea termine; Una vez que la tarea ha terminado, todos los hilos en espera pueden continuar con su negocio. En tal caso, usted debería utilizar notificationAll () para activar todos los hilos en espera al mismo tiempo.
Otro caso, por ejemplo, el bloqueo mutuamente exclusivo, solo uno de los hilos en espera puede hacer algo útil después de ser notificado (en este caso, adquirir el bloqueo). En tal caso, preferiría utilizar Notify () . Si se implementa de forma adecuada, también puede usar notifyAll () en esta situación, pero de forma innecesaria podría activar subprocesos 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 una llamada o.notifyAll()
despierta más de una o.notifyAll()
espera, y la primera que regresa de la línea o.wait()
hace que la condición salga en el estado falso, las otras cadenas que se despertaron volverán a la espera
Si uno busca en Google la "diferencia entre notify()
y notifyAll()
", entonces aparecerán muchas explicaciones (dejando aparte los párrafos de javadoc). Todo se reduce a la cantidad de subprocesos en espera que se están despertando: uno en notify()
y todos en notifyAll()
.
Sin embargo (si entiendo bien la diferencia entre estos métodos), solo se selecciona un hilo para la adquisición de monitores adicionales; en el primer caso, el seleccionado por la máquina virtual, en el segundo caso, el seleccionado por el programador de hilos del sistema. El programador desconoce los procedimientos de selección exactos para ambos (en el caso general).
¿Cuál es la diferencia útil entre notify() y notifyAll() entonces? ¿Me estoy perdiendo de algo?
Aquí hay un ejemplo. Ejecutarlo. Luego, cambie uno de los notificar a () para notificar () y ver 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 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) { }
}
}
}
Aquí hay una explicación más simple:
Está en lo cierto al decir que si utiliza "notificar () o notificarAll (), el resultado inmediato es que exactamente otro subproceso adquirirá el monitor y comenzará a ejecutarse. (Suponiendo que algunos subprocesos se bloquearon de hecho en espera () para este objeto, otros subprocesos no relacionados no están absorbiendo todos los núcleos disponibles, etc.) El impacto se produce más adelante.
Supongamos que el subproceso A, B y C estaban esperando en este objeto, y el subproceso A recibe el monitor. La diferencia radica en lo que sucede una vez que A libera el monitor. Si utilizó notificar (), B y C aún están bloqueados en esperar (): no están esperando en el monitor, están esperando que se les notifique. Cuando A libera el monitor, B y C seguirán sentados allí, esperando una notificación ().
Si usó notifyAll (), B y C han avanzado más allá del estado de "espera de notificación" y están a la espera de adquirir el monitor. Cuando A libera el monitor, B o C lo adquirirán (suponiendo que no haya otros subprocesos que compitan por ese monitor) y comiencen a ejecutarse.
Claramente, notify
wake (cualquiera) un subproceso en el conjunto de espera, notifyAll
despierta todos los subprocesos en el conjunto de espera. La siguiente discusión debe aclarar cualquier duda. notifyAll
que se debe utilizar la mayor parte del tiempo. Si no está seguro de cuál usar, use notifyAll
. notifyAll
favor, vea la explicación que sigue.
Lee con mucho cuidado y entiende. Por favor envíeme un correo electrónico si tiene alguna pregunta.
Mire productor / consumidor (el supuesto es una clase ProducerConsumer con dos métodos). ESTÁ ROTO (porque utiliza notify
): sí, PUEDE funcionar, incluso la mayoría 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 mientras rodea la espera?
Necesitamos un bucle while 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 pone en el conjunto de espera (a través de la llamada de wait
). 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 a notify
. El único hilo en espera es C1, por lo que está activado 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 (no deterministicamente) es elegido e ingresa al método, el otro está bloqueado (no esperando, pero bloqueado, tratando de adquirir el bloqueo del método). Digamos que C2 obtiene el bloqueo primero. C1 sigue bloqueando (intentando adquirir el bloqueo en X). C2 completa el método y libera el bloqueo. Ahora, C1 adquiere la cerradura. Adivina qué, afortunadamente tenemos un bucle while, porque, C1 realiza la verificación del bucle (guarda) y se evita que elimine un elemento inexistente del búfer (¡C2 ya lo consiguió!). Si no tuviéramos un while
, obtendríamos una IndexArrayOutOfBoundsException
ya que C1 intenta eliminar el primer elemento del búfer.
AHORA,
Ok, ahora ¿por qué necesitamos notificar a todos?
En el ejemplo de productor / consumidor anterior, parece que podemos evitar notify
. Parece de esta manera, porque podemos probar que los guardias en los bucles de espera para el productor y el consumidor son mutuamente exclusivos. Es decir, parece que no podemos tener un hilo esperando en el método put
, así como en el método get
, porque, para que eso sea cierto, lo siguiente debería ser cierto:
buf.size() == 0 AND buf.size() == MAX_SIZE
(asumiendo que MAX_SIZE no es 0)
SIN EMBARGO, esto no es lo suficientemente bueno, NECESITAMOS utilizar notifyAll
a notifyAll
. A ver 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 EN CUALQUIER MOMENTO se ha activado un subproceso con notificar, la JVM puede seleccionarlo de forma no determinista, es decir, que cualquier subproceso en espera se puede activar. También tenga en cuenta que cuando se bloquean varios subprocesos en la entrada a un método (es decir, al intentar adquirir un bloqueo), 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 un hilo ejecute (es decir, mantenga el bloqueo de) cualquier método (sincronizado) en la clase. Si ocurre la siguiente secuencia de eventos - resultados de interbloqueo:
PASO 1:
- P1 pone 1 char en el búfer.
PASO 2:
- P2 intenta put
- revisa el ciclo de espera - ya es un char - espera
PASO 3:
- P3 intenta put
- revisa el ciclo de espera - ya es un char - espera
ETAPA 4:
- C1 intenta obtener 1 char
- C2 intenta obtener 1 char - bloques en la entrada al método get
- C3 intenta obtener 1 char - bloques en la entrada al método get
PASO 5:
- C1 está ejecutando el método get
- obtiene el char, notify
llamadas, sale del método
- La 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 método put
- C2 comprueba el bucle de espera, no hay más caracteres en el búfer, así que espera
- C3 ingresa al método después de C2, pero antes de P2, comprueba el bucle de espera, no hay más caracteres en el búfer, así que espera
PASO 6:
- AHORA: ¡hay P3, C2 y C3 esperando!
- Finalmente, P2 adquiere el bloqueo, pone un char en el búfer, llama a Notify, sale del método.
PASO 7:
- La notificación de P2 despierta a P3 (recuerda que cualquier hilo puede ser despertado)
- P3 verifica la condición del bucle de espera, ya hay un char en el búfer, así que espera.
- ¡NO HAY MÁS HILOS PARA LLAMAR A NOTIFICAR Y TRES HILOS PERMANENTEMENTE SUSPENDIDOS!
SOLUCIÓN: Reemplace notify
con notify
notifyAll
en el código del productor / consumidor (arriba).
Creo que depende de cómo se producen y consumen los recursos. Si 5 objetos de trabajo están disponibles a la vez y tiene 5 objetos de consumidor, tendría sentido despertar todos los subprocesos mediante el uso de 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 del consumidor para que compitan por ese único objeto? El primero que compruebe el trabajo disponible lo obtendrá y todos los demás subprocesos comprobarán y encontrarán que no tienen nada que hacer.
He encontrado una gran explicación aquí . En breve:
El método notificar () se usa generalmente 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 en espera puede negociar con eso. El método notifyAll () se usa realmente en la mayoría de los otros casos. Estrictamente, se requiere que se notifique a los meseros de una condición que podría permitir que se desarrollen múltiples meseros. Pero a menudo esto es difícil de saber. Por lo tanto, como regla general, si no tiene una lógica particular para el uso de Notify () , probablemente debería usar notifyAll () , ya que a menudo es difícil saber exactamente qué subprocesos estarán esperando en un objeto en particular y por qué.
De Joshua Bloch, el mismo gurú de Java en Effective Java 2nd edition:
"Ítem 69: Prefiere las utilidades de concurrencia a esperar y notificar".
Diferencias útiles:
Use Notify () si todos sus hilos en espera son intercambiables (no importa el orden en que se despierten), o si solo tiene un hilo en espera. Un ejemplo común es un grupo de subprocesos que se utiliza para ejecutar trabajos desde una cola: cuando se agrega un trabajo, se notifica a uno de los subprocesos para que se active, ejecute el siguiente trabajo y vuelva a la suspensión.
Utilice notifyAll () para otros casos en los que los subprocesos en espera pueden tener diferentes propósitos y deben poder ejecutarse simultáneamente. Un ejemplo es una operación de mantenimiento en un recurso compartido, donde varios subprocesos esperan que la operación se complete antes de acceder al recurso.
Echa un vistazo al código publicado por @xagyg.
Supongamos que dos hilos diferentes están esperando dos condiciones diferentes:
El primer hilo está a la espera de buf.size() != MAX_SIZE
, y el segundo hilo está a la espera de buf.size() != 0
.
Supongamos que en algún punto buf.size()
no es igual a 0 . JVM llama notify()
notifyAll()
notify()
lugar de a notifyAll()
, y se notifica el primer hilo (no el segundo).
El primer hilo está buf.size()
, busca buf.size()
que puede devolver MAX_SIZE
y vuelve a esperar. El segundo hilo no se despierta, sigue esperando y no llama a get()
.
Espero que esto aclare algunas dudas.
notify() : el método notificar () despierta un hilo que espera el bloqueo (el primer hilo que llamó a la espera () en ese bloqueo).
notify() : el método notifyAll () activa todos los subprocesos que esperan el bloqueo; la JVM selecciona uno de los subprocesos de la lista de subprocesos que esperan el bloqueo y activa ese subproceso.
En el caso de un solo hilo que espera un bloqueo, no hay una diferencia significativa entre notificar () y notificarAll (). Sin embargo, cuando hay más de un subproceso esperando el bloqueo, en notificar () y notificarAll (), el subproceso exacto despertado está bajo el control de la JVM y no puede controlar mediante programación la activación de un subproceso específico.
A primera vista, parece que es una buena idea llamar a notificar () para activar un hilo; Puede parecer innecesario despertar todos los hilos. Sin embargo, el problema con la notificación () es que el subproceso despertado podría no ser el adecuado para ser despertado (el subproceso podría estar esperando alguna otra condición, o la condición aún no se cumple para ese subproceso, etc.). En ese caso , la notificación () podría perderse y ningún otro subproceso se activará, lo que podría generar un tipo de interbloqueo (la notificación se pierde y todos los demás subprocesos esperan la notificación, para siempre).
Para evitar este problema , siempre es mejor llamar a NotifyAll () cuando hay más de un hilo en espera de 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 despreciable en aplicaciones del mundo real.
Esta respuesta es una reescritura gráfica y una simplificación de la excelente respuesta de , incluidos los comentarios de eran .
¿Por qué utilizar notificar a todos, incluso cuando cada producto está destinado a un solo consumidor?
Considere productores y consumidores, simplificado de la siguiente manera.
Productor:
while (!empty) {
wait() // on full
}
put()
notify()
Consumidor:
while (empty) {
wait() // on empty
}
take()
notify()
Suponga que 2 productores y 2 consumidores comparten un búfer de tamaño 1. La siguiente imagen muestra un escenario que conduce a un interbloqueo , que se evitaría si todos los subprocesos utilizados notificaran a todos .
Cada notificación está etiquetada con el hilo que se está despertando.
Estoy muy sorprendido de que nadie haya mencionado el infame problema de "despertar perdido" (google).
Básicamente:
- Si tiene varios hilos en espera en una misma condición y,
- múltiples hilos que pueden hacer que pase del estado A al estado B y,
- varios subprocesos que pueden hacer que pase del estado B al estado A (normalmente los mismos subprocesos que en 1) y,
- la transición del estado A al B debe notificar los hilos en 1.
ENTONCES, debe usar notificar a todos, a menos que tenga garantías demostrables de que las activaciones perdidas son imposibles.
Un ejemplo común es una cola FIFO concurrente donde: varios encoladores (1. y 3. arriba) pueden hacer que su cola pase de vacío a varios vacíos no vacíos (2. arriba) pueden esperar la condición "la cola no está vacía" vacío -> no vacio deberia notificar a los extintores
Puede escribir fácilmente un entrelazado de operaciones en las que, a partir de una cola vacía, interactúan 2 encoladores y 2 encoladores y 1 encolador permanecerá en reposo.
Este es un problema posiblemente comparable con el problema del interbloqueo.
Hay tres estados para un hilo.
- WAIT - El hilo no está usando ningún ciclo de CPU
- BLOQUEADO: el subproceso se bloquea al intentar adquirir un monitor. Es posible que aún esté utilizando los ciclos de la CPU
- RUNNING - El hilo se está ejecutando.
Ahora, cuando se llama a Notify (), JVM selecciona un hilo y los mueve al estado BLOQUEADO y, por lo tanto, al estado EN EJECUCIÓN ya que no hay competencia para el objeto monitor.
Cuando se llama a NotifyAll (), JVM selecciona todos los subprocesos y los mueve a todos BLOQUEADOS. Todos estos subprocesos obtendrán el bloqueo del objeto por prioridad. El subproceso que sea capaz de adquirir el monitor primero podrá pasar al estado EN EJECUCIÓN primero y así sucesivamente.
Tenga en cuenta que con las utilidades de concurrencia también tiene la opción entre signal()
y signalAll()
ya que estos métodos se llaman allí. Así que la pregunta sigue siendo válida incluso con java.util.concurrent
.
Doug Lea presenta un punto interesante en su famoso libro : si una notify()
y Thread.interrupt()
suceden 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 costo de los gastos generales (despertando demasiados hilos la mayor parte del tiempo).
Todas las respuestas anteriores son correctas, por lo que puedo decir, así que les diré algo más. Para el código de producción realmente debería usar las clases en java.util.concurrent. Hay muy poco que no puedan hacer por ti, en el área de concurrencia en java.
notify()
despertará un hilo, mientras que notifyAll()
despertará todos. Por lo que sé, no hay un término medio. Pero si no está seguro de qué notify()
hará a sus subprocesos, use los notifyAll()
. Funciona como un encanto cada vez.
notify()
permite escribir un código más eficiente que los notifyAll()
.
Considere la siguiente pieza de código que se ejecuta desde múltiples hilos 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 sea 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 de 998, luego de 997, y así sucesivamente. Por el contrario, con la solución notify()
, solo se activará un hilo.
Use notifyAll()
cuando necesite elegir qué hilo realizará el siguiente trabajo:
synchronized(this) {
while(idx != last+1) // wait until it''s my turn
wait();
}
...
synchronized(this) {
last = idx;
notifyAll();
}
Finalmente, es importante comprender que en el caso de notifyAll()
, el código dentro de synchronized
bloques synchronized
que se han activado se ejecutará de forma secuencial, no todos a la vez. Digamos que hay tres subprocesos esperando en el ejemplo anterior, y el cuarto subproceso llama a notifyAll()
. Los tres hilos se activarán, pero solo uno iniciará la ejecución y verificará la condición del bucle while. Si la condición es true
, llamará a wait()
nuevamente, y solo entonces el segundo hilo comenzará a ejecutarse y verificará su condición de bucle while, y así sucesivamente.
notify()
- Selecciona un hilo aleatorio del conjunto de espera del objeto y lo pone 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 al BLOCKED
estado. Después de usar notifyAll()
, no quedan hilos en el conjunto de espera del objeto compartido porque todos están ahora en BLOCKED
estado y no en WAITING
estado.
BLOCKED
- Bloqueado para la adquisición de bloqueo. WAITING
- en espera de notificación (o bloqueado para completar la unión).
notify()
despierta el primer hilo que llama wait()
sobre el mismo objeto.
notifyAll()
despierta todos los hilos que llamaban wait()
al mismo objeto.
El hilo de mayor prioridad se ejecutará primero.
Tomado del blog sobre Java efectiva:
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 mencionado anteriormente, comentario de "Yann TM" sobre la respuesta aceptada y los docs Java):
- notificar (): JVM despierta uno de los subprocesos en espera en este objeto. La selección de hilos se realiza arbitrariamente sin equidad. Así el mismo hilo puede ser despertado una y otra vez. Entonces el estado del sistema cambia pero no se hace ningún progreso real. Así creando un livelock .
- notifyAll (): JVM despierta todos los subprocesos y luego todos los subprocesos compiten por el bloqueo en este objeto. Ahora, el programador de CPU selecciona un subproceso que adquiere bloqueo en este objeto. Este proceso de selección sería mucho mejor que la selección de JVM. Así, asegurando la vitalidad.
Breve resumen:
Prefiera notificar a Todos () a notificar () a menos que tenga una aplicación paralela masiva donde una gran cantidad de subprocesos hagan lo mismo.
Explicación:
notificar () [...] despierta un solo hilo. Debido a que notificar () no le permite especificar el subproceso que se ha despertado, es útil solo en aplicaciones paralelas masivas, es decir, programas con un gran número de subprocesos, todos haciendo tareas similares. En una aplicación de este tipo, no te importa qué hilo se despierte.
fuente: https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
Compare a Notify () con NotifyAll () en la situación descrita anteriormente: una aplicación masivamente paralela donde los hilos están haciendo lo mismo. Si llama a NotifyAll () en ese caso, notifyAll () provocará el despertar (es decir, la programación) de una gran cantidad de subprocesos, muchos de ellos innecesariamente (ya que solo un subproceso puede continuar, a saber, el subproceso que se le otorgará). monitorear el objeto esperar () , notificar () , o notificar a Todo () fue llamado en, por lo tanto, desperdiciando recursos informáticos.
Por lo tanto, si no tiene una aplicación en la que una gran cantidad de subprocesos haga lo mismo de forma concurrente, prefiera que notifique a Notar () a a notificar () . ¿Por qué? Porque, como otros usuarios ya han respondido en este foro, notificar ()
despierta un solo hilo que está esperando en el monitor de este objeto. [...] La elección es arbitraria 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 productor para el consumidor donde los consumidores están listos (es decir, esperando ) para consumir, los productores están listos (es decir, esperando ) para producir y la cola de elementos (a producir / consumir) está vacía. En ese caso, notificar () puede despertar solo a los consumidores y nunca a los productores porque la elección de quién se despierta es arbitraria . El ciclo de consumo del productor no haría ningún progreso, aunque los productores y los consumidores están listos para producir y consumir, respectivamente. En su lugar, un consumidor se despierta (es decir, deja el estado de espera () ), no quita un elemento de la cola porque está vacío y notifica a () que otro consumidor debe proceder.
En cambio, NotifyAll () despierta a productores y consumidores. La elección de quién está programado depende del programador. Por supuesto, dependiendo de la implementación del programador, el programador también puede programar a los consumidores (por ejemplo, si asigna una prioridad muy alta a las hebras de consumidores). Sin embargo, la suposición aquí es que el peligro de que el programador programe solo a los consumidores es menor que el peligro de que la JVM solo despierte a los consumidores porque cualquier programador implementado de manera razonable no toma decisiones arbitrarias . Más bien, la mayoría de las implementaciones del planificador hacen al menos un poco de esfuerzo para evitar la inanición.
Cuando se llama a la espera () del "objeto" (esperando que se obtenga el bloqueo del objeto), internamente esto liberará el bloqueo en ese objeto y la ayuda de los otros subprocesos para tener bloqueo en este "objeto", en este escenario habrá más de 1 subproceso esperando el "recurso / objeto" (considerando que los otros subprocesos también emitieron la espera en el mismo objeto anterior y en la forma en que habrá un subproceso que rellene el recurso / objeto e invoque a 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 solo hilo en espera y bloqueado (no todos los subprocesos en espera: este subproceso liberado será elegido por el hilo de JVM El programador y todo el proceso de obtención de bloqueos en el objeto es el mismo que el habitual.
Si solo tiene un subproceso que estará compartiendo / trabajando en este objeto, está bien usar el método notificar () solo en su implementación de espera-notificación.
Si se encuentra en una situación en la que más de un hilo lee y escribe sobre recursos / objetos en función de su lógica de negocios, debe ir a notificarlo a Todo
Ahora estoy viendo cómo exactamente el jvm está identificando y rompiendo el hilo en espera cuando emitimos notificar () en un objeto ...
Me gustaría mencionar lo que se explica en Java Concurrencia en la práctica:
Primer punto, ya sea Notificar o Notificar todo?
It will be NotifyAll, and reason is that it will save from signall hijacking.
Si dos subprocesos A y B están esperando en diferentes predicados de condición de la misma cola de condición y se llama a notificar, es hasta JVM a la que notificará JVM de subproceso.
Ahora, si la notificación fue para el subproceso A y JVM notificada el subproceso B, el subproceso B se activará y verá que esta notificación no es útil, por lo que esperará nuevamente. Y el subproceso A nunca llegará a conocer 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á a todos los subprocesos y todos los subprocesos competirán por el mismo bloqueo e implicará un cambio de contexto y, por lo tanto, cargará en la CPU. Pero debemos preocuparnos por el rendimiento solo si se está comportando correctamente, si el comportamiento en sí no es correcto, entonces el rendimiento no sirve para nada.
Este problema se puede resolver utilizando el objeto Condición del bloqueo explícito de bloqueo, 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á la señal y se asegurará de que solo un hilo esté esperando esa condición.
Para resumir las excelentes explicaciones detalladas anteriores, y de la manera más simple que se me ocurre, esto se debe a las limitaciones del monitor integrado de 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 que se espera / notifica en / acerca de.
Esto significa que si varios subprocesos están esperando en diferentes condiciones y se utiliza notificar (), el subproceso seleccionado puede no ser el que progresaría en la condición recién cumplida, lo que causará que ese subproceso (y otros subprocesos todavía en espera puedan ser procesados para cumplir la condición, etc.) no poder avanzar, y eventualmente morir de hambre o suspender el programa.
Por el contrario, notifyAll () permite que todos los subprocesos en espera vuelvan a adquirir el bloqueo y comprueben su condición respectiva, permitiendo así que se realicen progresos.
Por lo tanto, notifíquese () solo se puede usar de manera segura si se garantiza que cualquier hebra en espera permita el progreso en caso de que se seleccione, lo que en general se satisface cuando todas las hebras dentro del mismo monitor verifican solo una y la misma condición, algo bastante raro Caso en aplicaciones del mundo real.
notificar notificará solo un subproceso que está en estado de espera, mientras que notificar todos notificará todos los subprocesos en el 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 están en estado de espera anterior) estarán en estado bloqueado.
Despertar todo no tiene mucho significado aquí. Espere, notifique y notifique, todos estos se colocan después de poseer el monitor del objeto. Si un subproceso está en la etapa de espera y se llama a notificar, este subproceso retomará el bloqueo y ningún otro subproceso en ese momento podrá retomar ese bloqueo. Así que el acceso concurrente no puede tener lugar en absoluto. Por lo que sé, cualquier llamada para esperar notifica y notifica que solo se puede hacer notificar después de bloquear el objeto. Corrígeme si estoy equivocado.
Si bien hay algunas respuestas sólidas anteriores, me sorprende la cantidad de confusiones y malentendidos que he leído. Esto probablemente prueba la idea de que se debe usar java.util.concurrent tanto como sea posible en lugar de tratar de escribir su propio código concurrente roto. De vuelta a la pregunta: para resumir, la mejor práctica hoy es EVITAR notificar () en TODAS las situaciones debido a la pérdida del problema de activación. A cualquier persona que no entienda esto no se le debe permitir escribir código de concurrencia de misión crítica. Si está preocupado por el problema de la manada, una forma segura de lograr despertar un hilo a la vez es: 1. Crear una cola de espera explícita para los hilos en espera; 2. Haga que cada uno de los hilos en la cola espere a su predecesor; 3. Haga que cada subproceso llame a NotifyAll () cuando haya terminado. O puede usar Java.util.concurrent. *,que ya lo han implementado.