Java wait() explicado: cómo funciona, cómo usarlo de forma segura y diferencias con sleep()

目次

1. ¿Qué es wait() en Java? (Entiende la idea central rápidamente)

java wait se refiere a un método usado para pausar temporalmente un hilo (un flujo de ejecución) y esperar una notificación de otro hilo.
Se utiliza en multihilo (ejecutar varias tareas concurrentemente) para realizar control coordinado entre hilos.

El punto clave es que wait() no es “simplemente esperar a que pase el tiempo”.
Hasta que otro hilo llame a notify() o notifyAll(), el hilo objetivo permanece en estado de espera (WAITING).

1.1 El significado básico de wait()

La esencia de wait() se reduce a estos tres puntos:

  • Poner el hilo actual en un estado de espera
  • Liberar temporalmente el bloqueo del objeto objetivo
  • Reanudar cuando sea notificado por otro hilo

Flujo conceptual:

  1. Entrar en un bloque synchronized
  2. Llamar a wait()
  3. Liberar el bloqueo e ingresar al estado de espera
  4. Otro hilo llama a notify()
  5. Recuperar el bloqueo y luego reanudar la ejecución

Ejemplo de sintaxis simple:

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

⚠ Nota

  • wait() no es “pausar por un tiempo fijo”, sino “esperar una notificación”.
  • Si nadie notifica, puede esperar indefinidamente.

1.2 wait() es un método de Object

wait() no es un método de Thread.
Es un método de Object.

Definición (concepto):

public final void wait()

En otras palabras:

  • Cada objeto Java tiene wait()
  • Esperas basándote en el objeto que estás monitorizando

Conceptos erróneos comunes:

❌ Pensar que es Thread.wait()
❌ Pensar que “detiene un hilo” directamente

Entendimiento correcto:

✔ “Esperar en el monitor de un objeto (bloqueo del monitor)”

*Monitor = el bloqueo gestionado por synchronized.

1.3 Requisito obligatorio para usar wait()

Para usar wait(), debes llamarlo dentro de un bloque synchronized.

Por qué:

  • wait() libera el bloqueo que actualmente posees
  • Si no posees el bloqueo, no se puede garantizar la consistencia

Ejemplo incorrecto:

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

Ejemplo correcto:

Object obj = new Object();

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

Excepción lanzada:

IllegalMonitorStateException

Esto significa “wait() se llamó sin poseer el bloqueo.”

⚠ Dónde los principiantes suelen atascarse

  • Confundirlo con sleep() (sleep no libera el bloqueo)
  • Olvidar escribir synchronized
  • Nunca llamar a notify, por lo que se detiene indefinidamente
  • Llamar a notify sobre un objeto diferente

✔ Resumen más importante hasta ahora

  • wait() es un método de Object
  • Solo puede usarse dentro de synchronized
  • Libera el bloqueo y espera una notificación
  • No es un simple “retraso de tiempo”

2. Uso correcto de wait() (Entiende con ejemplos de código)

wait() no tiene sentido por sí solo.
Solo funciona cuando se combina con notify / notifyAll.

En este capítulo, lo explicaremos paso a paso, desde la configuración mínima hasta patrones prácticos.

2.1 Sintaxis básica

La estructura correcta más pequeña se ve así:

Object lock = new Object();

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

Sin embargo, esto se detendrá indefinidamente tal cual.
Razón: no hay notificación (notify).

2.2 Ejemplo con notify (Más importante)

wait() significa “esperar una notificación”.
Por lo tanto, otro hilo debe llamar a notify.

✔ Ejemplo 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);  // Esperar hasta 1 segundo

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);  // Esperar hasta 1 segundo
}

Behavior:

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

⚠ Importante
Incluso si devuelve por tiempo de espera, no significa que “la condición sea verdadera”.

Siempre combínelo con una verificación de la condición.

4.2 ¿Qué es un despertar espurio?

Un despertar espurio (Spurious Wakeup) es un fenómeno en el que
wait puede devolver incluso sin una notificación.

La especificación de Java permite que esto ocurra.

Eso significa:

  • Nadie llamó a notify
  • La condición no ha cambiado
  • Sin embargo, aún puede devolver

Por eso usar while en lugar de if es obligatorio.

4.3 El patrón correcto (envolver con while)

Ejemplo de implementación correcta:

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

Por qué:

  • Protección contra despertares espurios
  • La condición aún puede ser falsa después de notifyAll
  • Seguridad cuando varios hilos están esperando

❌ Ejemplo peligroso:

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

Este estilo puede conducir a un comportamiento incorrecto.

4.4 Relación con los deadlocks

wait libera un bloqueo, pero
los deadlocks aún pueden ocurrir cuando están involucrados varios bloqueos.

Ejemplo:

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

Mitigaciones:

  • Utilice un orden de adquisición de bloqueos consistente
  • Minimice la cantidad de bloqueos

⚠ Errores críticos comunes

  • No actualizar la variable de condición
  • Llamar a notify sin cambiar el estado
  • Anidar synchronized en exceso
  • No mantener el orden de bloqueos consistente entre varios bloqueos

✔ Resumen avanzado

  • Envolver wait con while es una regla absoluta
  • Incluso con tiempos de espera, debe volver a comprobar la condición
  • Un diseño de bloqueos deficiente puede causar deadlocks

5. Errores comunes y cómo solucionarlos

wait es una API de bajo nivel, por lo que los errores de implementación causan directamente bugs.
Aquí resumiremos los errores comunes, sus causas y cómo solucionarlos.

5.1 IllegalMonitorStateException

Este es el error más común.

Cuando ocurre:

  • Llamar a wait fuera de synchronized
  • Llamar a notify fuera de synchronized

❌ Ejemplo incorrecto

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

✔ Ejemplo correcto

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

Resumen de la solución:

  • Siempre llame a wait / notify dentro de synchronized sobre el mismo objeto
  • Utilice un único objeto de bloqueo consistente

5.2 No se reanuda incluso después de notify

Causas comunes:

  • Llamar a notify sobre un objeto diferente
  • La variable de condición no se actualiza
  • Mantener el bloqueo demasiado tiempo después de notify

❌ Error típico

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

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

✔ Siempre use el mismo objeto

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

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

5.3 wait nunca termina

Causas principales:

  • notify nunca se llama
  • Lógica de verificación de condición incorrecta
  • La condición del while permanece verdadera para siempre

Pasos de depuración:

  1. Confirme que notify se está llamando definitivamente
  2. Confirme que la variable de condición se está actualizando
  3. Use registros para rastrear el orden de ejecución de los hilos

5.4 Deadlocks

Patrón típico:

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

Si otro hilo adquiere los bloqueos en el orden opuesto, el programa puede congelarse.

Mitigaciones:

  • Utilice un orden de adquisición de bloqueos consistente
  • Utilice un único bloqueo si es posible
  • Considere usar java.util.concurrent

5.5 Manejo de InterruptedException

wait lanza InterruptedException.

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

Manejo recomendado:

  • Restaurar la bandera de interrupción (re-interrumpir)
  • No suprimir la excepción

⚠ Errores muy comunes en la práctica

  • Envolver wait con if
  • Llamar a notify antes de actualizar la condición
  • Gestionar múltiples bloqueos de forma ambigua
  • Usar un bloque catch vacío

✔ Reglas principales para evitar errores

  • synchronized es obligatorio
  • Usar siempre el mismo objeto de bloqueo
  • Siempre usar el patrón while
  • Actualizar el estado antes de llamar a notify
  • Manejar interrupciones correctamente

6. Alternativas modernas en Java (perspectiva práctica)

wait / notify son herramientas de coordinación de hilos de bajo nivel que existen desde los inicios de Java.
En el desarrollo real moderno, es común usar APIs más seguras y de mayor nivel en su lugar.

En este capítulo, resumiremos por qué se recomiendan las alternativas.

6.1 wait/notify son APIs de bajo nivel

Cuando usas wait, debes gestionar todo manualmente.

Cosas que debes gestionar:

  • Control de bloqueo
  • Variables de condición
  • Orden de notificación
  • Protección contra despertados espurios
  • Evitación de deadlocks
  • Manejo de interrupciones

En otras palabras, es un diseño que fácilmente se convierte en un caldo de cultivo para errores.

Se vuelve dramáticamente más complejo cuando intervienen múltiples condiciones y múltiples hilos.

6.2 Clases alternativas comunes

Desde Java 5, el paquete java.util.concurrent está disponible.

BlockingQueue

Para pasar datos entre hilos.

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

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

Características:

  • No se necesita wait/notify
  • Menos propenso a causar deadlocks
  • Muy usado en sistemas reales

CountDownLatch

Espera a que varios hilos terminen.

CountDownLatch latch = new CountDownLatch(1);

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

Casos de uso:

  • Esperar a que la inicialización se complete
  • Sincronizar tareas paralelas

ReentrantLock + Condition

Una API que brinda un mecanismo similar a wait, pero de forma más explícita.

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

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

Beneficios:

  • Puede gestionar múltiples condiciones
  • Control de bloqueo más flexible

6.3 ¿Qué deberías usar en la práctica?

Recomendaciones por escenario:

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

Conclusión:

  • Es importante entender wait mientras se aprende
  • En la práctica, prefiere el paquete concurrent

⚠ Criterios de decisión práctica

  • ¿Realmente necesitas implementar la coordinación de hilos tú mismo?
  • ¿Puede la biblioteca estándar resolverlo en su lugar?
  • ¿Puedes garantizar legibilidad y mantenibilidad?

En muchos casos, puedes resolver el problema sin usar wait.

✔ Resumen del capítulo

  • wait es una API de bajo nivel
  • El paquete concurrent es el estándar moderno
  • Prioriza la seguridad en el código real

7. Resumen (Notas finales para principiantes)

java wait es un mecanismo usado para esperar cambios de estado entre hilos.
El punto más importante es que no es un simple retraso temporal, sino un “mecanismo de control basado en notificaciones”.

Resumamos lo que cubrimos desde una perspectiva de implementación.

7.1 La esencia de wait

  • Un método de Object
  • Solo puede usarse dentro de synchronized
  • Libera el bloqueo y espera cuando se llama
  • Se reanuda mediante notify / notifyAll

7.2 Tres reglas para escribir código seguro

1) Siempre usar synchronized
2) Envolver con while
3) Actualizar el estado antes de llamar a notify

Plantilla correcta:

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

Lado del notificador:

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

7.3 La diferencia decisiva con sleep

Itemwaitsleep
Releases lockYesNo
Use caseInter-thread communicationTime delay

Es importante no confundirlos.

7.4 Dónde encaja en el desarrollo real

  • Entenderlo es esencial
  • Pero en la práctica, prioriza el paquete concurrent
  • Diseña un control personalizado basado en wait con mucho cuidado

7.5 Lista de Verificación Final

  • ¿Estás usando el mismo objeto de bloqueo?
  • ¿Lo llamas dentro de synchronized?
  • ¿Está envuelto en un while?
  • ¿Actualizas el estado antes de notify?
  • ¿Manejas las interrupciones correctamente?

Si sigues estos pasos, puedes reducir significativamente el riesgo de errores graves.

Preguntas frecuentes

Q1. ¿Es wait un método de la clase Thread?

No. Es un método de la clase Object.

Q2. ¿Puedo usar wait sin synchronized?

No. Lanzará IllegalMonitorStateException.

Q3. ¿Cuál debería usar: notify o notifyAll?

Si priorizas la seguridad, se recomienda notifyAll.

Q4. ¿Puede wait usarse con un tiempo de espera?

Sí. Usa wait(long millis).

Q5. ¿Por qué usar while en lugar de if?

Para protegerse de despertados espurios y de volver antes de que la condición se cumpla.

Q6. ¿Se usa wait comúnmente en código de producción moderno?

En los últimos años, usar java.util.concurrent se ha convertido en el enfoque predominante.

Q7. ¿Puedo reemplazarlo con sleep?

No. sleep no libera el bloqueo.