Java wait() explicado: como funciona, como usá-lo com segurança e diferenças em relação ao sleep()

目次

1. O que é Java wait()? (Entenda a Ideia Central Rapidamente)

java wait refere-se a um método usado para pausar temporariamente uma thread (um fluxo de execução) e aguardar uma notificação de outra thread.
É usado em multithreading (execução de múltiplas tarefas simultaneamente) para realizar controle coordenado entre threads.

O ponto chave é que wait() não é “apenas esperar o tempo passar.”
Até que outra thread chame notify() ou notifyAll(), a thread alvo permanece no estado de espera (WAITING).

1.1 O Significado Básico de wait()

A essência de wait() se resume a estes três pontos:

  • Colocar a thread atual em estado de espera
  • Liberar temporariamente o lock do objeto alvo
  • Retomar quando notificada por outra thread

Fluxo conceitual:

  1. Entrar em um bloco synchronized
  2. Chamar wait()
  3. Liberar o lock e entrar no estado de espera
  4. Outra thread chama notify()
  5. Readquirir o lock, então retomar a execução

Exemplo simples de sintaxe:

synchronized(obj) {
    obj.wait();
}

⚠ Nota

  • wait() não é “pausar por um tempo fixo”, mas “esperar por uma notificação.”
  • Se ninguém notificar, pode esperar para sempre.

1.2 wait() é um Método de Object

wait() não é um método de Thread.
É um método de Object.

Definição (conceito):

public final void wait()

Em outras palavras:

  • Todo objeto Java tem wait()
  • Você espera com base no objeto que está monitorando

Equívocos comuns:

❌ Pensar que é Thread.wait()
❌ Pensar que ele “para uma thread” diretamente

Entendimento correto:

✔ “Esperar no monitor de um objeto (lock do monitor)”

*Monitor = o lock gerenciado por synchronized.

1.3 Requisito Obrigatório para Usar wait()

Para usar wait(), você deve chamá-lo dentro de um bloco synchronized.

Por quê:

  • wait() libera o lock que você possui atualmente
  • Se você não possui o lock, a consistência não pode ser garantida

Exemplo incorreto:

Object obj = new Object();
obj.wait();   // ❌ IllegalMonitorStateException

Exemplo correto:

Object obj = new Object();

synchronized(obj) {
    obj.wait();
}

Exceção lançada:

IllegalMonitorStateException

Isso significa “wait() foi chamado sem possuir o lock.”

⚠ Onde Iniciantes Frequentemente Se Enrolam

  • Confundir com sleep() (sleep não libera o lock)
  • Esquecer de escrever synchronized
  • Nunca chamar notify, então fica parado para sempre
  • Chamar notify em um objeto diferente

✔ Resumo Mais Importante Até Agora

  • wait() é um método de Object
  • Só pode ser usado dentro de synchronized
  • Ele libera o lock e espera por uma notificação
  • Não é um simples “atraso de tempo”

2. Uso Correto de wait() (Entenda com Exemplos de Código)

wait() não faz sentido por si só.
Só funciona quando combinado com notify / notifyAll.

Neste capítulo, explicaremos passo a passo, desde a configuração mínima até padrões práticos.

2.1 Sintaxe Básica

A menor estrutura correta se parece com isto:

Object lock = new Object();

synchronized(lock) {
    lock.wait();
}

Entretanto, isso ficará parado para sempre como está.
Razão: não há notificação (notify).

2.2 Exemplo com notify (Mais Importante)

wait() significa “esperar por uma notificação.”
Portanto, outra thread deve chamar notify.

✔ Exemplo Mínimo Funcional

class Example {
    private static final Object lock = new Object();

    public static void main(String[] args) {

        Thread waitingThread = new Thread(() -> {
            synchronized(lock) {
                try {
                    System.out.println("Waiting...");
                    lock.wait();
                    System.out.println("Resumed.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread notifyingThread = new Thread(() -> {
            synchronized(lock) {
                System.out.println("Notificando...");
                lock.notify();
            }
        });

        waitingThread.start();
        notifyingThread.start();
    }
}

Execution Flow

  1. waitingThread acquires lock
  2. It calls wait() , releases the lock, and waits
  3. notifyingThread acquires lock
  4. It calls notify() to signal the waiting thread
  5. After re-acquiring the lock, execution resumes

2.3 Difference Between notify and notifyAll

MethodBehavior
notify()Resumes only one waiting thread
notifyAll()Resumes all waiting threads

In real-world code, notifyAll() is often considered safer.

Why:

  • With notify() , a thread that should not resume might be selected
  • When multiple conditions exist, inconsistencies become more likely

2.4 The Correct Practical Pattern (Use while)

wait() is typically used together with a condition check.

synchronized(lock) {
    while (!condition) {
        lock.wait();
    }
}

Why:

  • Spurious wakeups (resuming without notification) can happen
  • After notifyAll , the condition may still not be satisfied

❌ Incorrect example:

if (!condition) {
    lock.wait();
}

This is not safe.

⚠ Common Mistakes

  • Calling wait and notify on different objects
  • Assuming it resumes immediately after notify (it must re-acquire the lock)
  • Calling notify outside synchronized
  • Forgetting to update the condition variable

✔ Implementation Steps Summary

  1. Create a shared lock object
  2. Wrap with synchronized
  3. Check the condition with while
  4. Wait with wait()
  5. Notify from another thread using notify / notifyAll
  6. Always update the condition

3. The Difference Between wait and sleep (The Core Search Intent)

Many users who search for “java wait” want to understand how it differs from sleep().
They may look similar, but their design philosophy and use cases are completely different.

3.1 The Decisive Differences (Comparison Table)

Itemwaitsleep
Defined inObjectThread
Releases lockYesNo
Where to useMust be inside synchronizedCan be used anywhere
PurposeInter-thread communicationSimple time delay

3.2 The Biggest Difference: “Does It Release the Lock?”

✔ How wait Behaves

  • Releases the lock currently held
  • Allows other threads to acquire the lock
  • Resumes via notify
    synchronized(lock) {
        lock.wait();
    }
    

✔ How sleep Behaves

  • Stops while still holding the lock
  • Other threads cannot acquire the lock
    Thread.sleep(1000);
    

⚠ This is the most important point
sleep = “wait for time”
wait = “wait for a notification”

3.3 How to Choose in Real-World Work

✔ When to Use wait

  • Producer/Consumer patterns
  • Waiting for data to arrive
  • Waiting for a state change
  • Coordinated control between threads

✔ When to Use sleep

  • Simple delay processing
  • Adjusting retry intervals
  • Waiting in test code

3.4 Using wait with a Timeout

wait can also be called with a timeout.

lock.wait(1000);  // Wait up to 1 second

But note:

  • It may return after the timeout, but there is no guarantee the condition is satisfied
  • You must re-check the condition using while

⚠ Points That Confuse Beginners

  • Thinking sleep resumes via notification → ❌
  • Thinking wait always resumes after time → ❌
  • Thinking locks are released during sleep → ❌

✔ Conclusion

  • sleep is just a time delay
  • wait is a mechanism for inter-thread communication
  • The biggest difference is whether the lock is released

4. Advanced wait Knowledge (Must-Know for Safe Usage)

wait looks simple, but misuse can easily cause bugs.
Here we’ll explain the essential knowledge needed to implement it safely.

4.1 wait with a Timeout

wait has a timed version.

synchronized(lock) {
    lock.wait(1000);  // Wait up to 1 second
}

Behavior:

  • Waits up to the specified time
  • Returns immediately if notified
  • May also return when the time expires

⚠ Importante
Mesmo que retorne devido ao tempo limite, isso não significa que “a condição seja verdadeira.”

Sempre combine isso com uma verificação da condição.

4.2 O que é um Wakeup Espúrio?

Um wakeup espúrio (Spurious Wakeup) é um fenômeno onde
wait pode retornar mesmo sem uma notificação.

A especificação Java permite que isso aconteça.

Isso significa:

  • Ninguém chamou notify
  • A condição não mudou
  • Ainda assim pode retornar

Por isso usar while em vez de if é obrigatório.

4.3 O Padrão Correto (Envolver com while)

Exemplo de implementação correta:

synchronized(lock) {
    while (!condition) {
        lock.wait();
    }
    // Code after the condition becomes true
}

Por quê:

  • Proteção contra wakeups espúrios
  • A condição ainda pode ser falsa após notifyAll
  • Segurança quando múltiplas threads estão aguardando

❌ Exemplo perigoso:

synchronized(lock) {
    if (!condition) {
        lock.wait();  // Dangerous
    }
}

Esse estilo pode levar a comportamento incorreto.

4.4 Relação com Deadlocks

wait libera um lock, mas
deadlocks ainda podem acontecer quando múltiplos locks estão envolvidos.

Exemplo:

  • Thread A → lock1 → lock2
  • Thread B → lock2 → lock1

Mitigações:

  • Use uma ordem consistente de aquisição de locks
  • Minimize o número de locks

⚠ Erros Críticos Comuns

  • Não atualizar a variável de condição
  • Chamar notify sem mudar o estado
  • Aninhar synchronized demais
  • Não manter a ordem de locks consistente entre múltiplos locks

✔ Resumo Avançado

  • Envolver wait com while é uma regra absoluta
  • Mesmo com timeouts, você deve reavaliar a condição
  • Um design de lock ruim pode causar deadlocks

5. Erros Comuns e Como Corrigi-los

wait é uma API de baixo nível, portanto erros de implementação causam bugs diretamente.
Aqui resumiremos erros comuns, suas causas e como corrigi-los.

5.1 IllegalMonitorStateException

Este é o erro mais comum.

Quando isso acontece:

  • Chamando wait fora de synchronized
  • Chamando notify fora de synchronized

❌ Exemplo incorreto

Object lock = new Object();
lock.wait();  // Exception thrown

✔ Exemplo correto

synchronized(lock) {
    lock.wait();
}

Resumo da correção:

  • Sempre chame wait / notify dentro de synchronized no mesmo objeto
  • Use um único objeto lock consistente

5.2 Não Retoma Mesmo Após notify

Causas comuns:

  • Chamando notify em um objeto diferente
  • A variável de condição não é atualizada
  • Manter o lock por muito tempo após notify

❌ Erro típico

synchronized(lock1) {
    lock1.wait();
}

synchronized(lock2) {
    lock2.notify();  // Different object
}

✔ Sempre use o mesmo objeto

synchronized(lock) {
    lock.wait();
}

synchronized(lock) {
    condition = true;
    lock.notifyAll();
}

5.3 wait Nunca Termina

Principais causas:

  • notify nunca é chamado
  • Lógica de verificação de condição incorreta
  • A condição do while permanece verdadeira para sempre

Passos de depuração:

  1. Confirme que notify está definitivamente sendo chamado
  2. Confirme que a variável de condição está sendo atualizada
  3. Use logs para rastrear a ordem de execução das threads

5.4 Deadlocks

Padrão típico:

synchronized(lock1) {
    synchronized(lock2) {
        ...
    }
}

Se outra thread adquirir locks na ordem oposta, o programa pode travar.

Mitigações:

  • Use uma ordem consistente de aquisição de locks
  • Use um único lock se possível
  • Considere usar java.util.concurrent

5.5 Tratamento de InterruptedException

wait lança InterruptedException.

try {
    lock.wait();
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

Manipulação recomendada:

  • Restaurar a bandeira de interrupção (re-interrupt)
  • Não suprimir a exceção

⚠ Erros Muito Comuns na Prática

  • Envolvendo wait com if
  • Chamando notify antes de atualizar a condição
  • Gerenciando múltiplos locks de forma ambígua
  • Usando um bloco catch vazio

✔ Regras Principais para Evitar Erros

  • synchronized é obrigatório
  • Use o mesmo objeto de lock consistentemente
  • Sempre use o padrão while
  • Atualize o estado antes de chamar notify
  • Trate interrupções adequadamente

6. Alternativas Modernas em Java (Perspectiva Prática)

wait / notify são ferramentas de coordenação de threads de baixo nível que existem desde o início do Java.
No desenvolvimento real moderno, é comum usar APIs mais seguras e de nível superior em vez disso.

Neste capítulo, resumiremos por que as alternativas são recomendadas.

6.1 wait/notify São APIs de Baixo Nível

Quando você usa wait, deve gerenciar tudo manualmente.

Coisas que você deve gerenciar:

  • Controle de lock
  • Variáveis de condição
  • Ordem de notificação
  • Proteção contra wakeups espúrios
  • Evitar deadlocks
  • Manipulação de interrupções

Em outras palavras, é um design que facilmente se torna um terreno fértil para bugs.

Ele se torna dramaticamente mais complexo quando múltiplas condições e múltiplas threads estão envolvidas.

6.2 Classes Alternativas Comuns

Desde o Java 5, o pacote java.util.concurrent está disponível.

BlockingQueue

Para passar dados entre threads.

BlockingQueue<String> queue = new LinkedBlockingQueue<>();

queue.put("data");   // Waiting is handled automatically
queue.take();        // Waits until data arrives

Características:

  • Não precisa de wait/notify
  • Menos propenso a causar deadlocks
  • Muito usado em sistemas reais

CountDownLatch

Aguarda que múltiplas threads concluam.

CountDownLatch latch = new CountDownLatch(1);

latch.await();   // Wait
latch.countDown();  // Signal

Casos de uso:

  • Esperar a inicialização ser concluída
  • Sincronizar tarefas paralelas

ReentrantLock + Condition

Uma API que fornece um mecanismo semelhante ao wait, mas de forma mais explícita.

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
    condition.await();
} finally {
    lock.unlock();
}

Benefícios:

  • Pode gerenciar múltiplas condições
  • Controle de lock mais flexível

6.3 O Que Você Deve Usar na Prática?

Recomendações por cenário:

GoalRecommended Tool
Passing dataBlockingQueue
Simple synchronizationCountDownLatch
Complex condition controlReentrantLock + Condition
LearningUnderstanding wait is essential

Conclusão:

  • É importante entender wait enquanto aprende
  • Na prática, prefira o pacote concurrent

⚠ Critérios Práticos de Decisão

  • Você realmente precisa implementar a coordenação de threads por conta própria?
  • A biblioteca padrão pode resolvê-lo em vez disso?
  • Você pode garantir legibilidade e manutenção?

Em muitos casos, você pode resolver o problema sem usar wait.

✔ Resumo do Capítulo

  • wait é uma API de baixo nível
  • O pacote concurrent é o padrão moderno
  • Priorize a segurança no código real

7. Resumo (Notas Finais para Iniciantes)

java wait é um mecanismo usado para aguardar mudanças de estado entre threads.
O ponto mais importante é que não se trata de um simples atraso de tempo, mas de um “mecanismo de controle baseado em notificação”.

Vamos resumir o que cobrimos a partir de uma perspectiva de implementação.

7.1 A Essência do wait

  • Um método de Object
  • Só pode ser usado dentro de synchronized
  • Libera o lock e aguarda quando chamado
  • Retoma via notify / notifyAll

7.2 Três Regras para Escrever Código Seguro

1) Sempre use synchronized
2) Envolva com while
3) Atualize o estado antes de chamar notify

Modelo correto:

synchronized(lock) {
    while (!condition) {
        lock.wait();
    }
}

Lado do notificador:

synchronized(lock) {
    condition = true;
    lock.notifyAll();
}

7.3 A Diferença Decisiva em Relação ao sleep

Itemwaitsleep
Releases lockYesNo
Use caseInter-thread communicationTime delay

É importante não confundi-los.

7.4 Onde Ele se Encaixa no Desenvolvimento do Mundo Real

  • Compreender isso é essencial
  • Mas na prática, priorize o pacote concurrent
  • Desenhe controles personalizados baseados em wait com muito cuidado

7.5 Lista de Verificação Final

  • Você está usando o mesmo objeto de bloqueio?
  • Você está chamando isso dentro de synchronized ?
  • Está envolvido com while ?
  • Você atualiza o estado antes de notify ?
  • Você está lidando com interrupções adequadamente?

Se você seguir essas, você pode reduzir significativamente o risco de bugs graves.

FAQ

Q1. wait é um método da classe Thread?

Não. É um método da classe Object.

Q2. Posso usar wait sem synchronized?

Não. Isso lançará IllegalMonitorStateException.

Q3. Qual devo usar: notify ou notifyAll?

Se você prioriza a segurança, notifyAll é recomendado.

Q4. wait pode ser usado com um tempo limite?

Sim. Use wait(long millis).

Q5. Por que usar while em vez de if?

Para proteger contra despertares espúrios e retornar antes que a condição seja satisfeita.

Q6. wait é comumente usado em código de produção moderno?

Nos últimos anos, usar java.util.concurrent se tornou a abordagem principal.

Q7. Posso substituí-lo por sleep?

Não. sleep não libera o bloqueio.