Está en la página 1de 8

Primitivas de sincronización

Introducción
La programación concurrente se da cuando múltiples actividades cooperan dentro de
una misma aplicación. Para que dicha cooperación sea posible, esas actividades
tendrán que comunicarse y sincronizarse. Por tanto, habrá que diseñar algunos
mecanismos que resuelvan tales necesidades de comunicación y sincronización, como
es el caso de los monitores.

Nos centraremos en un caso de asignación de turnos automáticos en una entidad


gubernamental. Aquí tomaremos el problema de productor/consumidores con buffer
limitado, en el que el productor (proceso de asignación de turnos) irá generando
números (turnos para cada ventanilla) de uno en uno y los consumidores (ventanillas)
podrán acceder a esos números. El productor no podrá producir más de un número y los
consumidores no podrán consumir nada, si no se ha producido antes.

1. Monitores
Un monitor es una estructura de datos que ofrece un conjunto de operaciones
públicas. […] Garantiza que sus operaciones se ejecutarán en exclusión mutua.
[…] Además, ofrece otra construcción interna: las condiciones. Cada condición
permite suspender hilos dentro del monitor liberando el acceso a este
(sincronización condicional). (Muñoz Escoí et al., 2012, p. 46)
Es importante aclarar que:
Resulta conveniente integrar la gestión de la sincronización en un lenguaje de
programación orientado a objetos. El run-time [tiempo de ejecución] del lenguaje
permitirá utilizar los mecanismos de sincronización necesarios para garantizar
exclusión mutua en el acceso a objetos compartidos y el soporte necesario para la
sincronización condicional, requiriendo un esfuerzo mínimo por parte del
programador. (Muñoz Escoí et al., 2012, pp. 48-49)
Entonces, podemos decir que “un monitor, en un lenguaje orientado a objetos, es una
clase que permite definir objetos que se compartirán de manera segura entre múltiples
hilos” (Muñoz Escoí et al., 2012, p. 49).

Esta clase tiene las siguientes características:

Garantizan que sus métodos se ejecuten en exclusión mutua. Disponen


de una cola de entrada donde quedarán esperando aquellos hilos que
intenten entrar en el monitor cuando ya haya otro hilo ejecutando alguno de
sus métodos. Al proporcionar esa exclusión mutua, se impide que haya
condiciones de carrera dentro del monitor.
Resuelven la sincronización condicional. El programador deberá decidir
dónde y cómo se utilizan las condiciones dentro de los métodos del monitor.
(Muñoz Escoí et al., 2012, p. 49)

Ahora bien, ¿cómo podríamos aplicar nuestro caso de ejemplo de manera concreta?
Recordando que tenemos que implementar el problema denominado
productor/consumidor con buffer limitado, en el que el productor (turnero) irá generando
números de uno en uno y los consumidores (ventanillas) podrán acceder a esos
números. El productor no podrá producir más de un número y los consumidores no
podrán consumir nada, si no se ha producido antes.

En primer lugar, debemos tener presente la sincronización entre estos


procesos/actividades, tal como nos solicita el problema. Para ello, creamos la clase
«Buffer», que será donde el productor generará los números y de donde los
consumidores (ventanillas) los leerán. A continuación, se explica cómo crear esta clase
(Goetz, Peierls, Bloch, Bowbeer, Holmes y Lea, 2006):

public class Buffer


{
private int contenido;
private boolean bufferlleno = Boolean.FALSE;
/**
* Obtiene de forma concurrente o síncrona el elemento que hay en el buffer
* @return contenido el buffer
*/
public synchronized int get()
{
while (!bufferlleno)
{
try { wait(); }
catch (InterruptedException e)
{ System.err.println("Buffer: Error en get -> " + e.getMessage()); }
}
bufferlleno = Boolean.FALSE;
notify();
return contenido;
}

/**
* Introduce de forma concurrente o síncrona un elemento en el buffer
* @param value Elemento a introducir en el contenedor
*/
public synchronized void put(int value)
{
while (bufferlleno)
{
try
{
wait();
}
catch (InterruptedException e)
{
System.err.println("Buffer: Error en put -> " + e.getMessage());
}
}
contenido = value;
bufferlleno = Boolean.TRUE;
notify();
}
}

Ahora, creamos la clase «Productor» (Goetz et al., 2006):

public class Productor implements Runnable


{
private final Random aleatorio;
private final Buffer contenedor;
private final int idproductor;
private final int TIEMPOESPERA = 3000;// equivale a 3 segundos

/**
* Constructor de la clase
* @param contenedor Buffer común a los consumidores y el productor
* @param idproductor Identificador del productor
*/
public Productor(Buffer contenedor, int idproductor)
{
this.contenedor = contenedor;
this.idproductor = idproductor;
aleatorio = new Random();
}

@Override
/**
* Implementación del hilo
*/
public void run()
{
while(Boolean.TRUE)
{
int poner = aleatorio.nextInt(41);//tenemos 41 números por atender.
contenedor.put(poner);
System.out.println("El productor " + idproductor + " genera el número: " +
poner);
try
{
Thread.sleep(TIEMPOESPERA);
}
catch (InterruptedException e)
{
System.err.println("Productor " + idproductor + ": Error en run -> " +
e.getMessage());
}
}
}
}

Continuamos con la clase «Consumidor» (Goetz et al., 2006):

public class Consumidor implements Runnable


{
private final Buffer contenedor;
private final int idconsumidor;

/**
* Constructor de la clase
* @param contenedor Contenedor común a los consumidores y el productor
* @param idconsumidor Identificador del consumidor
*/
public Consumidor(Buffer contenedor, int idconsumidor)
{
this.contenedor = contenedor;
this.idconsumidor = idconsumidor;
}

@Override
/**
* Implementación del hilo
*/
public void run()
{
while(Boolean.TRUE)
{
System.out.println("La ventanilla " + idconsumidor + " llama al número: " +
contenedor.get());
}
}
}

Ahora, que tenemos el main de nuestro programa, denominaremos a la clase


«ProductorConsumidor» (Goetz et al., 2006):

public class ProductorConsumidor


{
private static Buffer contenedor;
private static Thread productor;
private static Thread [] consumidores;
/**
* asumimos que existen 4 ventanillas, las denominamos consumidores
*/
private static final int CANTIDADCONSUMIDORES = 4;
public static void main(String[] args)
{
contenedor = new Buffer();
productor = new Thread(new Productor(contenedor, 1));
consumidores = new Thread[CANTIDADCONSUMIDORES];

for(int i = 0; i < CANTIDADCONSUMIDORES; i++)


{
consumidores[i] = new Thread(new Consumidor(contenedor, i));
consumidores[i].start();
}

productor.start();
}

Variantes de monitor
“Todo monitor debe mantener suspendido temporalmente (hasta que el otro se suspenda
o abandone el monitor) uno de los dos hilos involucrados en los efectos del método
notify(): el invocador (o notificador) o bien el que debía ser reactivado” (Muñoz Escoí et
al., 2012, p. 58). Es por esto que existen variantes que dan solución a esta situación,
cuyas características se describen en la tabla 1.

Tabla 1: Características de las variantes de monitor


Fuente: adaptación propia con base en Muñoz Escoí et al., 2012, p. 59

Las variantes de Brinch Hansen y Hoare priorizan al hilo que debía ser reactivado,
mientras que la variante de Lampson y Redell (que es la utilizada en los POSIX
threads y en Java) da mayor prioridad al hilo notificador.

[…]

Los monitores pueden presentar problemas si se realizan invocaciones anidadas.


Así, si desde el código de alguno de los métodos de un monitor A se necesita
utilizar algún método de otro monitor B y el hilo que lo ejecuta llegara a quedarse
suspendido en alguna de las condiciones del monitor B, ningún otro hilo podría
ejecutar el monitor A, pudiéndose producir situaciones de interbloqueo. (Muñoz
Escoí et al., 2012, pp. 59 y 69)
En la lectura 4 de este módulo ahondaremos sobre este último punto.

Referencias
Goetz B., Peierls, T., Bloch, J., Bowbeer, J., Holmes, D. y Lea, D. (2006). Java
Concurrency in Practice. Estados Unidos: Addison-Wesley Professional

Muñoz Escoí, F. D., Argente Villaplana, E., Espinosa Minguet, A., Galdámez Saiz, P.,
García-Fornes, A., De Juan Marín, R. y Sendra Roig, J. (2012). Concurrencia y
sistemas distribuidos. Valencia, España: Universitat Politècnica

También podría gustarte