Ejemplo modelo Productor Consumidor en Java

Hola a todos, hoy os voy a hablar sobre el modelo Productor Consumidor en Java.

Productor Consumidor es un modelo de comunicación entre hilos donde un hilo produce valores y otro hilo donde consume esos valores. Todo esto de manera sincronizada.

Ambos hilos tratan de manejar valores de una zona común que se le suele llamar Monitor (también se le suele llamar Buffer) que se encarga de sincronizar la entrada y salida de valores.

Sino recuerdas como funcionan los hilos, te dejo un tutorial aquí.

Crear nuestro propio hilo en Java

También es importante conocer el funcionamiento synchronized en Java, te dejo otro tutorial:

https://www.discoduroderoer.es/synchronized-en-java/

Veamos un ejemplo codificado de Productor Consumidor. 

Clase Buffer (Monitor)

La clase Buffer se encarga de gestionar la entrada y salida de datos, con synchronized hacemos que solo entre un hilo a ejecutar la función cuando varios intentan entrar a la vez.

Cuando esta lleno, ejecutamos un wait para que el resto espere y cuando se consuma un valor, este notificará a todos con notifyAll que se puede producir de nuevo.

En este caso, la estructura de datos es un array de char pero puede ser aquello que necesites.

public class Buffer {

    private char buffer[];
    private int siguiente;
    
    // Flags para saber el estado del buffer
    private boolean estaLlena;
    private boolean estaVacia;

    public Buffer(int tamanio) {
        buffer = new char[tamanio];
        siguiente = 0;
        estaLlena = false;
        estaVacia = true;
    }

    // Método para retirar letras del buffer
    public synchronized char consumir() {
        // No se puede consumir si el buffer está vacío
        while (estaVacia == true) {
            try {
                wait(); // Se sale cuando estaVacia cambia a false
            } catch (InterruptedException e) { }
        }
        // Decrementa la cuenta, ya que va a consumir una letra
        siguiente--;
        // Comprueba si se retiró la última letra
        if (siguiente == 0) {
            estaVacia = true;
        }
        // El buffer no puede estar lleno, porque acabamos
        // de consumir
        estaLlena = false;
        notifyAll();

        // Devuelve la letra al thread consumidor
        return (buffer[siguiente]);
    }

    // Método para añadir letras al buffer
    public synchronized void producir(char c) {
        // Espera hasta que haya sitio para otra letra
        while (estaLlena == true) {
            try {
                wait(); // Se sale cuando estaLlena cambia a false
            } catch (InterruptedException e) {
            
            }
        }
        // Añade una letra en el primer lugar disponible
        buffer[siguiente] = c;
        // Cambia al siguiente lugar disponible
        siguiente++;
        estaVacia = false;
        // Comprueba si el buffer está lleno
        if (siguiente == this.buffer.length) {
            estaLlena = true;
        }
        estaVacia = false;
        notifyAll();
    }
}
Clase Productor

La clase Productor hereda de Thread para que sea un hilo, le pasamos el buffer como parámetro. El buffer es quien se encarga de añadir el valor producido.

public class Productor extends Thread {

    private Buffer buffer;
    private final String letras = "abcdefghijklmnopqrstuvxyz";

    public Productor(Buffer buffer) {
        this.buffer = buffer;
    }

    public void run() {
        while (true) {
            // Obtenemos una letra al azar
            char c = letras.charAt((int) (Math.random() * letras.length()));
            // Producimos un valor
            buffer.producir(c);
            System.out.println("Depositado el caracter " + c + " del buffer");
            try {
                // Esperamos entre 0 y 4 segundos 
                sleep((int) (Math.random() * 4000));
            } catch (InterruptedException e) { }
        }
    }
}
Clase Consumidor

La clase Consumidor hereda de Thread para que sea un hilo, le pasamos el buffer como parámetro. El buffer es quien se encarga de quitarlo del valor producido.

public class Consumidor extends Thread {

    private Buffer buffer;

    public Consumidor(Buffer buffer) {
        this.buffer = buffer;
    }

    public void run() {

        while (true) {
            // Consume el valor si es posible
            char valor = buffer.consumir();
            System.out.println("Recogido el caracter " + valor + " del buffer");
            try {
                // Esperamos entre 0 y 4 segundos
                sleep((int) (Math.random() * 4000));
            } catch (InterruptedException e) { }
        }
    }
}
Clase ejecutable
public class EjemploProductorConsumidor {

    public static void main(String[] args) {
        Buffer b = new Buffer(10);
        Productor p = new Productor(b);
        Consumidor c = new Consumidor(b);
        try {
            
            p.start();
            c.start();
        
        } catch (InterruptedException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

Este seria el resultado (puede variar):

Te dejo también un video, donde lo comento paso a paso.

Espero que os sea de ayuda. Si tenéis dudas, preguntad. Estamos para ayudarte.

Compartir

1 comentario

  1. diana

    mi caso es el siguiente, quiero ahcer un post a una WS desde otra WS a traves de una aplicacion Java, pero el POST solo me permite hacer 60 posts cada 40s, tengo un worker que carga los 60 productos y duerme los 40 s, mi pregunta es si a traves del Productor-Consumidor podría capturar esos 60 productos mientras el worker esta dormido. O hay otra manera de hacerlo?

Deja una respuesta

Tu dirección de correo electrónico no será publicada.