Gruki Logo

Gruki - Documentación de Programación

Java

Comunicación entre Hilos en Java: Wait() y Notify()

Aprende cómo los hilos en Java pueden hablarse entre sí usando wait() y notify(). ¡Es como tener un walkie-talkie para tus programas!

En Java, a veces queremos que diferentes partes de nuestro programa, llamadas hilos, trabajen juntas. Imagina que tienes dos amigos: uno cocina una pizza y el otro la espera para comer. Necesitan una forma de comunicarse: el cocinero debe avisar cuando la pizza está lista, y el que espera debe saber cuándo empezar a comer.

Aquí es donde entran en juego `wait()` y `notify()`. `wait()` es como decir 'voy a esperar aquí hasta que alguien me diga que puedo continuar'. `notify()` es como decir '¡Oye, el que está esperando, ya puedes seguir!'.

Piénsalo como una fila en una heladería. Algunas personas esperan en la fila (hilos en estado de espera) hasta que el heladero (otro hilo) los llama uno por uno (notify()).

El Poder de Wait()

`wait()` hace que un hilo se detenga, liberando temporalmente el 'turno' (el monitor) que tiene. Mientras espera, el hilo no consume recursos del procesador.

Imagina que estás jugando a las escondidas. El que busca (el hilo) dice '¡Voy a contar hasta diez!', mientras los demás (otros hilos) se esconden (entran en estado `wait()`). Mientras cuenta, el que busca deja de 'buscar' activamente, dando a los demás tiempo para esconderse.

Puntos importantes:

  • Es importante llamar a `wait()` dentro de un bloque `synchronized`. Esto es como asegurarte de que solo una persona puede usar el walkie-talkie a la vez.
  • Además, siempre debes poner `wait()` dentro de un bucle `while`. ¿Por qué? Porque otro hilo podría 'despertarte' (con `notify()`) por error. El bucle `while` verifica si realmente estás listo para seguir.

Notify() y NotifyAll()

`notify()` despierta a uno de los hilos que están esperando en el mismo objeto. Es como el heladero llamando al siguiente en la fila.

`notifyAll()` despierta a *todos* los hilos que están esperando en el mismo objeto. Es como si el heladero gritara '¡Helados gratis para todos!'.

Piensa en un semáforo. Cuando la luz se pone verde (`notify()`), un coche puede avanzar. Si todos están esperando y la luz se pone verde (`notifyAll()`), todos los coches pueden arrancar a la vez.

Puntos importantes:

  • Al igual que `wait()`, `notify()` y `notifyAll()` también deben llamarse dentro de un bloque `synchronized`.
  • Usar `notifyAll()` es generalmente más seguro que `notify()`, aunque puede ser menos eficiente. Asegura que ningún hilo se quede esperando innecesariamente.

Ejemplo con un Buffer

Un ejemplo común es un buffer compartido entre un productor y un consumidor. El productor añade elementos al buffer y el consumidor los retira.

Si el buffer está lleno, el productor espera (`wait()`) hasta que el consumidor retire un elemento. Si el buffer está vacío, el consumidor espera (`wait()`) hasta que el productor añada un elemento.

Cuando el productor añade un elemento, notifica (`notifyAll()`) al consumidor. Cuando el consumidor retira un elemento, notifica (`notifyAll()`) al productor.

Puntos importantes:

  • Este ejemplo ilustra como hilos pueden cooperar y no interferir entre sí usando los monitores que ofrece Java

Ejemplos de Código

Ejemplo sencillo de comunicación entre un productor y un consumidor usando wait() y notify().

Lenguaje: Java
java

Explicación del código:

  1. Línea 1: `class Buffer`: Define la clase Buffer, que actúa como el espacio compartido entre el productor y el consumidor.
  2. Línea 2: `private int contenido;`: Este es el valor que se comparte, la pizza.
  3. Línea 3: `private boolean disponible = false;`: Indica si hay un valor disponible en el buffer (si la pizza está lista). Inicialmente, no hay pizza.
  4. Línea 4: `synchronized int get() { ... }`: Método para que el consumidor obtenga (coma) el valor (la pizza). `synchronized` significa que solo un hilo puede ejecutar este código a la vez.
  5. Línea 5: `while (!disponible) { ... }`: Si no hay pizza ( `!disponible` es verdadero), el consumidor espera.
  6. Línea 6: `try { wait(); } catch (InterruptedException e) {}`: El consumidor se pone en modo espera. Es como sentarse en la mesa esperando que llegue la pizza. El `wait()` libera el 'turno' del buffer para que otro hilo pueda usarlo.
  7. Línea 7: `disponible = false;`: Una vez que el consumidor obtiene la pizza, se marca como no disponible.
  8. Línea 8: `notifyAll();`: Avisa a todos los hilos que estén esperando (en este caso, al productor) que algo ha cambiado.
  9. Línea 9: `return contenido;`: El consumidor 'devuelve' la pizza (en realidad, se la come), y el método devuelve el valor.
  10. Línea 10: `synchronized void put(int valor) { ... }`: Método para que el productor ponga (cocine) un valor (una pizza).
  11. Línea 11: `while (disponible) { ... }`: Si ya hay una pizza (`disponible` es verdadero), el productor espera. No puede cocinar otra pizza hasta que se hayan comido la anterior.
  12. Línea 12: `contenido = valor;`: El productor pone la pizza en el buffer.
  13. Línea 13: `disponible = true;`: Ahora hay una pizza disponible.
  14. Línea 14: `notifyAll();`: Avisa a todos los hilos que estén esperando (en este caso, al consumidor) que hay una nueva pizza.
  15. Línea 15: `class Productor implements Runnable { ... }`: Define la clase Productor que implementa la interfaz Runnable para poder ser ejecutada en un hilo.
  16. Línea 16: `private Buffer buffer;`: Cada productor tiene un Buffer para dejar la pizza.
  17. Línea 17: `public void run() { ... }`: Este método se ejecuta cuando se inicia el hilo del productor.
  18. Línea 18: `for (int i = 1; i <= 10; i++) { ... }`: El productor produce 10 pizzas.
  19. Línea 19: `buffer.put(i);`: El productor pone la pizza número `i` en el buffer.
  20. Línea 20: `System.out.println("Productor puso: " + i);`: Muestra en la consola que el productor ha puesto una pizza.
  21. Línea 21: **`Thread.sleep((int)(Math.random() * 100));`**: El productor se toma un pequeño descanso después de cocinar cada pizza (simula el tiempo de cocción).
  22. Línea 22: `class Consumidor implements Runnable { ... }`: Define la clase Consumidor que implementa la interfaz Runnable para poder ser ejecutada en un hilo.
  23. Línea 23: `private Buffer buffer;`: Cada consumidor tiene un Buffer para obtener la pizza.
  24. Línea 24: `public void run() { ... }`: Este método se ejecuta cuando se inicia el hilo del consumidor.
  25. Línea 25: `for (int i = 1; i <= 10; i++) { ... }`: El consumidor consume 10 pizzas.
  26. Línea 26: `int valor = buffer.get();`: El consumidor obtiene una pizza del buffer.
  27. Línea 27: `System.out.println("Consumidor obtuvo: " + valor);`: Muestra en la consola que el consumidor ha obtenido una pizza.
  28. Línea 28: **`Thread.sleep((int)(Math.random() * 100));`**: El consumidor se toma un pequeño descanso después de comer cada pizza (simula el tiempo de digestión).
  29. Línea 29: `public class Main { ... }`: Clase principal que inicia el programa.
  30. Línea 30: `public static void main(String[] args) { ... }`: Método principal que se ejecuta cuando se inicia el programa.
  31. Línea 31: `Buffer buffer = new Buffer();`: Crea un buffer compartido.
  32. Línea 32: `Productor productor = new Productor(buffer);`: Crea un productor que usa el buffer.
  33. Línea 33: `Consumidor consumidor = new Consumidor(buffer);`: Crea un consumidor que usa el buffer.
  34. Línea 34: `Thread hiloProductor = new Thread(productor);`: Crea un hilo para el productor.
  35. Línea 35: `Thread hiloConsumidor = new Thread(consumidor);`: Crea un hilo para el consumidor.
  36. Línea 36: `hiloProductor.start();`: Inicia el hilo del productor.
  37. Línea 37: `hiloConsumidor.start();`: Inicia el hilo del consumidor.

Recursos relacionados

Explora más contenido relacionado con Comunicación entre hilos con wait() y notify(). para profundizar tus conocimientos.