Ejemplo de wait() y notify() en Java

Voy a hacer un código que muestre cómo usar wait() y notify() desde dos hilos diferentes de un programa Java. En la lección anterior os contaba cómo usar estos métodos, que tienen que ver con el monitor de una instancia, y que por lo tanto sólo tienen sentido cuando tienes más de un hilo en tu programa.

En la programación concurrente en Java, uno de los retos más comunes es coordinar la ejecución de varios hilos que comparten recursos para evitar errores y condiciones de carrera. Un ejemplo clásico es cuando un hilo produce un dato y otro hilo debe consumirlo, pero el consumidor no puede acceder al dato hasta que el productor lo haya generado. Para ilustrar esta situación, vamos a trabajar con un ejemplo sencillo que involucra dos hilos y una variable compartida llamada bandeja.

Imaginemos que tenemos dos hilos: uno llamado entrega y otro llamado recibe. El hilo entrega se encarga de depositar una contraseña en la bandeja, mientras que el hilo recibe debe obtener esa contraseña para procesarla. El problema surge si el hilo recibe intenta acceder a la contraseña antes de que el hilo entrega la haya colocado. En nuestro diseño inicial, el método getPassword de la bandeja verifica si la contraseña está vacía y, si es así, lanza una excepción o se queja, lo que puede provocar que el programa falle si el hilo receptor es demasiado rápido.

Para evitar esta situación, necesitamos una forma de sincronizar ambos hilos para que el receptor espere hasta que la contraseña esté disponible. Aquí es donde entran en juego los métodos wait() y notify() de Java, que permiten que un hilo se suspenda y espere una señal de otro hilo para continuar.

Primero, definimos la clase Bandeja con un campo privado para la contraseña y métodos para establecer y obtener la contraseña. No sincronizamos los métodos directamente, sino que usaremos un objeto de bloqueo compartido, que en este caso será la propia instancia de Bandeja (referida como b), para controlar el acceso concurrente.

public class Bandeja {
    private String password = "";

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword() {
        if (password.isEmpty()) {
            throw new IllegalStateException("La contraseña no está disponible");
        }
        return password;
    }
}

Luego, en el hilo receptor, antes de intentar obtener la contraseña, entramos en un bloque sincronizado sobre b y verificamos si la contraseña está disponible. Si no lo está, llamamos a b.wait(), lo que hace que el hilo receptor se suspenda y libere el monitor de b, esperando a que otro hilo lo notifique.

synchronized (b) {
    while (b.getPassword().isEmpty()) {
        try {
            System.out.println("Receptor me espero");
            b.wait();
            System.out.println("Receptor esperado");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    String pwd = b.getPassword();
    System.out.println("Contraseña recibida con " + pwd.length() + " dígitos");
}

Por otro lado, el hilo entrega, después de establecer la contraseña, debe notificar a cualquier hilo que esté esperando en b para que pueda continuar. Esto también se hace dentro de un bloque sincronizado sobre b, llamando a b.notify().

synchronized (b) {
    System.out.println("Pongo la clave");
    b.setPassword("1234");
    b.notify();
}

Con esta estructura, el hilo receptor no intentará obtener la contraseña hasta que el hilo entrega la haya puesto y haya llamado a notify(). Esto evita que el programa falle por acceder a datos no disponibles y mejora la coordinación entre hilos.

Es importante recordar que tanto wait() como notify() deben llamarse dentro de bloques sincronizados sobre el mismo objeto monitor, en este caso b. Además, cuando un hilo llama a wait(), libera el monitor para que otros hilos puedan entrar en la sección sincronizada y, cuando otro hilo llama a notify(), despierta a uno de los hilos que están esperando en ese monitor.

Aunque este mecanismo es básico y no es la forma más sofisticada de manejar concurrencia en Java, es muy útil para entender cómo se puede controlar la ejecución y comunicación entre hilos sin necesidad de clases adicionales. Forma parte de la clase Object y es fundamental para implementar patrones de sincronización simples.

En resumen, al usar wait() y notify() podemos hacer que un hilo espere pacientemente hasta que otro hilo le indique que puede continuar, lo que nos permite evitar errores comunes en la programación concurrente y mejorar la fiabilidad de nuestros programas.

Lista de reproducción
  1. 1
    ¿Qué es la concurrencia?
    8 minutos
  2. 2
    ¿Qué es un hilo?
    5 minutos
  3. 3
    Crear un hilo en Java
    10 minutos
  4. 4
    Interrupción de hilos
    10 minutos
  5. 5
    Cómo usar Thread.join
    14 minutos
  6. 6
    Corrupción de memoria
    8 minutos
  7. 7
    Monitores y synchronized
    10 minutos
  8. 8
    Bloque synchronized
    12 minutos
  9. 9
    Preguntas típicas sobre synchronized en Java
    8 minutos
  10. 10
    Interbloqueos, synchronized y el problema de la cena de los filósofos
    13 minutos
  11. 11
    Introducción a Executor en Java
    11 minutos
  12. 12
    Introducción al uso de ExecutorService en Java
    14 minutos
  13. 13
    Introducción a Thread Pools en Java
    10 minutos
  14. 14
    Cómo crear Thread Pools en Java
    11 minutos
  15. 15
    ¿Cómo funcionan wait() y notify()?
    10 minutos
  16. 16
    Ejemplo de wait() y notify() en Java
    9 minutos
  17. 17
    La palabra clave volatile
    8 minutos
  18. 18
    Cómo usar Future
    8 minutos
  19. 19
    Variables atómicas
    7 minutos