domingo, 25 de mayo de 2014

Ejercicio de Concurrencia en Java: Agentes y fumadores con Monitores y Locks

Os dejo dos soluciones diferentes a un ejercicio de Concurrencia en Java. Una es usando monitores y la otra usando locks/condiciones. La verdad es que cambian bastante poco ambas soluciones pero así se tienen más claras las diferencias entre ambos métodos.
Os dejo el enunciado y los dos códigos, primero el de locks y luego el de monitores.


Considera un sistema formado por tres hebras fumadores que se pasan el día liando cigarros y fumando. Para liar un cigarro necesitan tres ingredientes: tabaco, papel y cerillas. Cada fumador dispone de un surtido suficiente (para el resto de su vida) de uno de los tres ingredientes. Cada fumador tiene un ingrediente diferente, es decir, un fumador tiene una cantidad infinita de tabaco, el otro de papel y el otro de cerillas. Hay también una hebra agente que pone dos de los tres ingredientes encima de una mesa. El agente dispone de unas reservas infinitas de cada uno de los tres ingredientes y escoge de forma aleatoria cuáles son los ingredientes que pondrá encima de la mesa.Cuando los ha puesto, el fumador que tiene el otro ingrediente puede fumar (los otros dos no). Para ello coge los ingredientes, se lía un cigarro y se lo fuma. Cuando termina de fumar vuelve a repetirse el ciclo. En resumen, el ciclo que debe repetirse es :
“agente pone ingredientes fumador hace cigarro fumador → → fuma → fumador
termina de fumar → agente pone ingredientes → ...”
Es decir, en cada momento a lo sumo hay un fumador fumando un cigarrillo.

Soluciones:

Está claro que siempre hay múltiples soluciones a un mismo problema, así que os ofrezco la manera que yo he tenido de hacerlo.

Para este problema he creado 4 clases diferentes: Agente, Fumador, Main y SalaFumadores. Tanto para monitores como para locks, las tres primeras son iguales, simplemente cambia la clase SalaFumadores, pues es donde hacemos la sincronización con estos dos métodos distintos.

Os dejo primero las clases comunes y posteriormente las dos clases SalaFumadores diferentes.

Agente.java


import java.util.Random;


public class Agente extends Thread {
 private SalaFumadores sala;
 private Random r;
 public Agente(SalaFumadores sala){
  this.sala = sala;
  r= new Random();
 }
 public void run (){
  while(true){
   sala.colocar(r.nextInt(3)+1);
  }
 }
}


Fumador.java
public class Fumador extends Thread{
 private int id;
 private SalaFumadores sala;
 public Fumador(int id, SalaFumadores sala){
  this.id = id;
  this.sala = sala;
 }
 public void run(){
  while(true){
   try {
    sala.entrafumar(id);
    System.out.println("Fumador "+id+" está fumando.");
    Thread.sleep(1000);
    sala.terminafumar(id);
   } catch (InterruptedException e) { e.printStackTrace(); }
  }
 }
}

Main.java
public class Main {
 public static void main(String[] args) {
  SalaFumadores sala = new SalaFumadores();
  Fumador fumador1 = new Fumador(1, sala);
  Fumador fumador2 = new Fumador(2, sala);
  Fumador fumador3 = new Fumador(3, sala);
  Agente agente = new Agente(sala);
  fumador1.start();
  fumador2.start();
  fumador3.start();
  agente.start();
  
 }
}
SalaFumadores.java con locks/condiciones.
import java.util.concurrent.locks.*;
 

public class SalaFumadores {
 /*
  * NOTA:
  * Para simplificar el problema considero que los ingredientes son tres enteros 1, 2 y 3.
  * Cada fumador vendrá representado por el número de ingrediente que tiene, es decir,
  * el fumador 1 posee el ingrediente 1.
  * Además en la mesa de la sala el agente pone dos ingredientes más, por lo que la mesa
  * se identificará con el entero del ingrediente que falta, es decir, si mesa = 1 significa
  * que en la mesá están los ingredientes 2 y 3, pero no el 1. Para representar que la mesa 
  * está vacía ponemos la variable mesa a 0.
  */
 
 static int mesa; //representa que ingrediente no esta
 static Lock l;
 static Condition [] puedofumar;
 static boolean alguienFuma;
 static Condition puedocolocar;
 
 public SalaFumadores(){
  l = new ReentrantLock(true);
  puedofumar =  new Condition[3];
  puedofumar[0] = l.newCondition();
  puedofumar[1] = l.newCondition();
  puedofumar[2] = l.newCondition();
  puedocolocar= l.newCondition();
  mesa = 0;
  alguienFuma = false;
 }
 
 public void entrafumar(int id){
  l.lock();
  try{
   while(mesa != id || alguienFuma){
    try {
     puedofumar[id-1].await();
    } catch (InterruptedException e) {e.printStackTrace(); }
   }
   //ya puedo fumar
   
   mesa = 0; //cojo los ingredientes
   alguienFuma = true;

   
  
  }finally{
   l.unlock();
  }
 }
 
 public void terminafumar(int id){
  l.lock();
  try {
   alguienFuma = false;
   puedocolocar.signal();
  } finally{
   l.unlock();
  }
 }
 
 public void colocar (int ingrediente){ // ingrediente que falta en la mesa
  l.lock();
  
  try{
   while( mesa !=0 || alguienFuma){
    try {
     puedocolocar.await();
    } catch (InterruptedException e) {e.printStackTrace(); }
   }
   mesa = ingrediente;
   System.out.println("En la mesa falta el ingrediente "+ mesa);
   
   puedofumar[mesa-1].signal();
   
  }finally{
   l.unlock();
  }
  
 }
}
SalaFumadores.java con Monitores
public class SalaFumadores {
 /*
  * NOTA:
  * Para simplificar el problema considero que los ingredientes son tres enteros 1, 2 y 3.
  * Cada fumador vendrá representado por el número de ingrediente que tiene, es decir,
  * el fumador 1 posee el ingrediente 1.
  * Además en la mesa de la sala el agente pone dos ingredientes más, por lo que la mesa
  * se identificará con el entero del ingrediente que falta, es decir, si mesa = 1 significa
  * que en la mesá están los ingredientes 2 y 3, pero no el 1. Para representar que la mesa 
  * está vacía ponemos la variable mesa a 0.
  */
 public static int mesa = 0; // indicará qué elemento falta en la mesa
 public static boolean alguienFumando = false;
 
 
 
 public synchronized void entrafumar( int ingrediente){
  while(mesa != ingrediente || alguienFumando){
   try {
    //System.out.println("El fumador "+ ingrediente + " no puede fumar aún.");
    wait();
   } catch (InterruptedException e) {e.printStackTrace();} 
  }
  // se hace el cigarro
  mesa = 0; //mesa vacía
  //fuma
  alguienFumando = true;
 }
 public synchronized void terminafumar(){
  alguienFumando = false;
  notifyAll();
 }
 
 public synchronized void colocar(int noesta){
  while(mesa != 0 || alguienFumando){
   try {
    //System.out.println("No puedo poner ingredientes aún");
    wait();
   } catch (InterruptedException e) {e.printStackTrace();} 
  }
  mesa = noesta;
  System.out.println("En la mesa no hay ingrediente "+ mesa);
  notifyAll();
 }
}

4 comentarios:

  1. Hola,
    estoy liado con los monitores y estoy teniendo algunos problemas, como estoy tiene más de un año igual mi comentario cae en saco roto, pero por probar...

    Yo tambien estoy haciendo el problema de los fumadores, lo tengo programado un poco distinto, pero el problema parece ser que no funcionan los notifyAll().

    en lo que tu llamas entrafumar yo tengo algo asi:

    while(!ingEnMesa&&ingrediente!=this.ingrediente) {
    System.out.println("fumador de " + ingrediente + " esperando...");
    wait();
    }
    // al salir se supone que esta fumando
    System.out.println("estoy fumando " + ingrediente);
    fumando=true;
    ingEnMesa=false;

    en la de colocar tengo algo asi:
    while(ingEnMesa||fumando){
    System.out.println("estanquero esperando...");
    wait();
    }
    this.ingrediente=generarIngredienteRandom();
    System.out.println("Se autoriza a fumar al fumador con " + this.ingrediente);
    ingEnMesa=true;
    notifyAll();

    y en la de terminar:
    fumando = false;
    notifyAll();


    Practicamente lo mismo como puedes comprobar, y mi salida es esta:
    fumador de cerillas esperando...
    fumador de pipa esperando...
    fumador de tabaco esperando...
    se autoriza a fumar al fumador con tabaco
    estanquero esperando

    es decir, parece que los tres procesos fumador se quedan esperando pues ingEnMesa es falso, pero cuando lo pongo a true desde colocar y utilizo el notifyAll() parece que no les llega nada y siguen en el while sin parar. Alguna idea de que pasa?

    ResponderEliminar
    Respuestas
    1. Lo primero, si tienes sospechas de que el "notifyAll()" no funciona, comprueba que todas las hebras compartan la clase que las controla, en este caso llamada SalaFumadores, que como ves se pasa como argumento en el main.
      Seguramente, en tu código, en cada fumador y en el estanquero inicializas en cada uno de ellos la clase SalaFumador que usas.
      Sin embargo, todos tienen que usar la misma, pues en el problema "todos usan la misma mesa", que es lo que controla dicha clase.
      No puedes usar mesas distintas para cada fumador, pues la mesa es común para todos.

      Además debes cambiar el operador del siguiente while:
      while (!ingEnMesa && ing!=ingrediente)
      Lo que significa que el fumador debería esperar mientras en la mesa no haya ingredientes y no estén los que le faltan.
      Sin embargo debería ser un OR, pues no deben darse ambas condiciones, por ejemplo con que no haya ingredientes ya el fumador debe esperar, independientemente de la otra condición. Por tanto el while correcto sería:
      while (!ingEnMesa || ing!=ingrediente)

      Eliminar
  2. Hola, quiero saber si se puede dejar el metodo de los ingredientes Random, al igual que los proveedores, para que así no siempre el Fumador 1 tenga el ingrediente 1, gracias.

    ResponderEliminar
    Respuestas
    1. Mmm... A ver como lo explico...

      Da igual qué fumador tenga qué ingrediente. Funcionaría exactamente igual. Si defines que el fumador 1 tiene el ingrediente 2, el 2, el 3; y el 3, el 1; funcionaría igual.

      Los ingredientes no son más que números asociados a fumadores. Puedes darles la definición que quieras, que la lógica es la misma.

      No se si consigo explicarme bien :S

      Saludos;)

      Eliminar