"¡Hola, amigo!"

Y un par de detalles más. Llamémoslo consejos prácticos.

"Suponga que tiene un método que está esperando algo y se duerme hasta que se cumple una condición".

Si la colección está vacía, entonces esperamos
public synchronized Runnable getJob()
{
 if (jobs.size() == 0)
  this.wait();

 return jobs.remove(0);
}

"La documentación de Java recomienda con mucha insistencia llamar al método de espera en un bucle:"

Si la colección está vacía, entonces esperamos
public synchronized Runnable getJob()
{
 while (jobs.size() == 0)
  this.wait();

 return jobs.remove(0);
}

"¿Por qué? La cuestión es que si el hilo se despierta, eso no significa que la condición esté satisfecha. Tal vez había veinte de esos hilos dormidos. Todos se despertaron, pero solo uno puede asumir la tarea".

"En términos generales, puede haber 'falsas alarmas'. Un buen desarrollador debe tener esto en cuenta".

"Ya veo. ¿No es más fácil simplemente usar notificar?"

"Bueno, ¿y si hubiera más de una tarea en la lista? Por lo general, se recomienda usar Notificar por el bien de la optimización. En todos los demás casos, se recomienda usar el método de notificación a todos".

"DE ACUERDO."

"Pero hay más. En primer lugar, puede haber una situación en la que alguien herede su clase, agregue sus propios métodos y también use wait/notifyAll. En otras palabras, puede haber una situación en la que los pares independientes wait/notifyAll esperen en el mismo objeto. y no saben el uno del otro. Entonces, ¿qué debes hacer?

"¡Siempre llame a esperar en un ciclo y verifique que la condición de terminación del ciclo sea verdadera!"

"Correcto. Y para dejar bastante claro que no puedes escapar de esto, muchos desarrolladores señalan que a veces los subprocesos se activan solos. Subprocesos que están garantizados para no activarse accidentalmente. Esto parece ser un efecto secundario de la optimización del código en un ejecutando una máquina Java".

"Vaya. Lo tengo. Sin un bucle, el método de espera no es bueno".