"Olá, amigo!"

"E mais alguns detalhes. Vamos chamar de conselho prático."

"Suponha que você tenha um método que esteja esperando por algo e adormeça até que uma condição seja satisfeita."

Se a coleção estiver vazia, então esperamos
public synchronized Runnable getJob()
{
 if (jobs.size() == 0)
  this.wait();

 return jobs.remove(0);
}

"A documentação do Java recomenda insistentemente chamar o método wait em um loop:"

Se a coleção estiver vazia, então esperamos
public synchronized Runnable getJob()
{
 while (jobs.size() == 0)
  this.wait();

 return jobs.remove(0);
}

"Por quê? O problema é que, se o fio acordar, isso não significa que a condição foi satisfeita. Talvez houvesse vinte desses fios adormecidos. Todos eles acordaram, mas apenas um pode assumir a tarefa."

"Falando grosso modo, pode haver 'alarmes falsos'. Um bom desenvolvedor deve levar isso em conta."

"Entendo. Não é mais fácil apenas usar o notify?"

"Bem, e se houver mais de uma tarefa na lista? Notify é geralmente recomendado para fins de otimização. Em todos os outros casos, é recomendado usar o método notifyAll."

"OK."

"Mas há mais. Primeiro, pode haver uma situação em que alguém herda sua classe, adiciona seus próprios métodos e também usa wait/notifyAll. Em outras palavras, pode haver uma situação em que pares independentes wait/notifyAll esperam no mesmo objeto e não se conhecem. Então, o que você deve fazer?"

"Sempre chame wait em um loop e verifique se a condição de término do loop é verdadeira!"

"Certo. E para deixar bem claro que você não pode escapar disso, muitos desenvolvedores apontam que, às vezes, os threads são ativados por si mesmos. Threads que garantem que não serão ativados acidentalmente. Isso parece ser um efeito colateral da otimização de código em um executando uma máquina Java."

"Uau. Entendi. Sem um loop, o método de espera não é bom."