Monitores
Definición
Un monitor es un mecanismo de software para
control de concurrencia que contiene los datos
y los procedimientos necesarios para realizar
la asignación de un determinado recurso o
grupo de recursos compartidos reutilizables
en serie.
La Noción de un monitor fue presentada
Uso de los Monitores
Un monitor se usa para manejar todas las
funciones de concurrencia, comunicación
entre procesos y localización física de
recursos en una región crítica.
Para llevar a cabo la asignación de un recurso
Los datos contenidos en el monitor pueden
ser
globales
(accesibles a
todos
los
procedimientos dentro del monitor), o locales
(accesibles a un procedimiento específico).
Estos datos sólo son accesibles dentro del
Estructura del Monitor
El monitor consta de varios procedimientos
que manipulan datos internos y existe una
parte de inicialización.
El código del monitor consta
de 2 partes lógicas:
• El algoritmo para la
manipulación del recurso.
Postergación Indefinida.
Para asegurar que un proceso obtenga el
Variables de Condición
Dado que un proceso puede necesitar esperar
fuera del monitor por varias razones se creo un
mecanismo llamado variable de condición.
Una variable de condición se controla con solo
dos operaciones:
wait(variable) Forma el proceso en la lista de espera
de la variable de condición.
signal(variable) Libera al primer proceso de la lista de
lala variable de decisión. Esta operación implica la
salida del proceso actual del monitor.
Normalmente se usa una variable de condición
Variables de Condición
Si el proceso que se encuentra dentro del
monitor encuentra que el recurso que ocupa no
está disponible, el procedimiento del monitor
debe sacar momentáneamente al proceso y
hacerlo esperar fuera del monitor (wait).
El proceso NO puede permanecer en el monitor
ya que esto viola la condición de exclusión
mutua y debe esperar fuera del monitor la
liberación del recurso que necesita.
En el estado activo existen tres opciones para que un proceso abandone el monitor:
1. SALIR (exit): en este caso un proceso llega al final del procedimiento de monitor que está ejecutando. Por ejemplo, en la salida normal de una región crítica .
2. CONTINUAR (continue): Esta operación se realiza cuando el proceso activo completó una acción que otro proceso pudiera estar esperando (en el estado bloqueado) y entonces le envía la señal (signal) de que puede continuar. Al proceso bloqueado se le permite regresar a activo. Si no hay procesos bloqueados se abre la entrada del monitor.
3. RETARDAR (delay): El retardo se presenta cuando un proceso necesita el resultado de una acción que otro proceso aún no realiza y entonces espera (wait).
El proceso que desbloquea a otro a través de la operación continuar
debe dejar el monitor
Problema de los Lectores y Escritores
La solución de este problema se puede usar como un modelo para implementar un mecanismo de acceso a información compartida.
Problema: Existen varios procesos lectores y escritores que acceden una base de datos común, las reglas de acceso son:
1. Cualquier número de procesos lectores puede acceder la base de datos en forma concurrente.
2. Cuando un proceso escritor está accediendo la base de datos ningún otro proceso, ni lector, ni escritor podrá accederla.
3. Cuando están accediendo procesos lectores y se tenga por lo menos a un escritor esperando acceso, los lectores que llegaron después del escritor no acceden directamente sino esperan.
4. Los procesos lectores esperando que un escritor termine su acceso tienen prioridad sobre el siguiente escritor.
Problema de los Filósofos
Existen n filósofos que pasan su vida pensando y comiendo.
Cada filósofo tiene su lugar en una mesa circular en cuyo
centro está un plato con espagueti.
Para comer espagueti se requieren dos Tenedores, pero sólo
hay n tenedores, uno entre cada par de filósofos.
Los únicos tenedores que puede tomar un filósofo son los
inmediatos a su izquierda y derecha.
El problema es simular el comportamiento de un filósofo
igual para todos, de tal forma que:
Se
evite
el
interbloqueo,
por
ejemplo,
cuando
simultáneamente cada filósofo toma su tenedor izquierdo y
se queda esperando por su tenedor derecho.
Se evite la inanición esto es, que un filósofo no pueda
Monitores en Java
Un monitor es un objeto que implementa el
acceso en exclusión mutua a sus métodos.
En Java se aplica a los métodos de la clase
Monitores en Java
Disciplinas de señalización de monitores son:
Signal and exit : Después de hacer notify el
thread debe salir del monitor y el thread que
recibe la señal de que es el siguiente en entrar en
el monitor.
Signal and continue: El thread que recibe la señal
no tiene que ser necesariamente el siguiente en
entrar en el monitor.
Java trabaja el modelo de Signal y Continue, lo
Monitores en Java
Todos los objetos tienen un bloqueo asociado,
lock o cerrojo, que puede ser adquirido y liberado
mediante el uso de métodos y sentencias
synchronized.
La sincronización obliga a que la ejecución de los
dos hilos sea mutuamente exclusiva en el tiempo.
Mecanismos de bloqueo:
Métodos synchronized (exclusión mutua).
Bloques synchronized (regiones críticas).
Mecanismos de comunicación de los threads
(variables de condición):
Código Sincronizado en Java
Java soporta la exclusión mutua mediante la
palabra reservada synchronized que puede
aplicarse a un método completo, o a una
secuencia de instrucciones.
Sincronización del método
class Monitor {
private int valor= 0 ;
public synchronized void suma ( ) {
valor++; }
En Java hay una cerradura (lock) por objeto.
Cuando un thread llama a un método
sincronizado, espera hasta obtener el acceso
exclusivo
al objeto
que lo contiene.
Una vez que se ha ejecutado el método, se libera
el cerrojo.
Sincronización del objetoclass Monitor {
private int valor= 0 ; public void suma ( ) {
Synchronized (this) { valor++;}
Condiciones de sincronización
Cuando una hebra que tiene el lock
de un objeto y debe suspenderse
debido a alguna condición de
sincronización, se introduce en el
conjunto de espera del objeto,
llamando al método:
void wait()
Cuando una hebra ejecuta wait():
Ejemplo Productor-Consumidor
El productor genera un entero entre 0 y 9, lo
almacena en un objeto Monitor_Almacen, e
imprime el número generado, el productor
duerme durante un tiempo aleatorio entre 0 y
100 milisegundos antes de repetir el ciclo de
generación de números.
El
consumidor,
por
su
parte,
está
Clase Monitor_Almacen
class Monitor_Almacen {
private int numero;
private boolean disponible = false;
public synchronized int get() { while (disponible == false) { try {
wait();
} catch (InterruptedException e) { }
}
disponible = false; notify();
return numero; }
public synchronized void put(int valor {
while (disponible == true) { try {
wait();
} catch (InterruptedException e) {
} }
numero = valor; disponible = true; notify();
Clase Productor
class Productor extends Thread { private Monitor_Almacen monitor1; private int num;
public Productor(Monitor_Almacen m, int num1) {
monitor1 = m; this.num = num1; }
public void run() {
for (int i = 0; i < 10; i++) { monitor.put(i);
System.out.println("Productor #"+this.num+" pone: " +i); try {
sleep((int)(Math.random() * 100)); } catch (InterruptedException e) { }
Consumidor
class Consumidor extends Thread { private Monitor_Almacen monitor1 ; private int num;
public Consumidor(Monitor_Almacen m, int num1) {
monitor1 = m; this.num = num1; }
public void run() { int valor = 0;
for (int i = 0; i < 10; i++) {
valor = monitor1.get();
System.out.println("Consumidor #"+this.num+" obtiene:"+valor); }
Implementar Clase Principal
public static void main(String args[ ] )
{
Monitor_Almacen m = Monitor_Almacen( );
new Productor (m);
new Consumidor(m);
}
Ejemplo 2
Elroblema productor/consumidor2 con java.
Se compone de cuatro clases simples:
Cola_Monitor,cuyo acceso se trata de sincronizar a
sus métodos, es el monitor.
Productor, es un hilo que genera datos para la cola
Consumidor, es un hilo que consume datos de la
cola.
ProCon, crea una Cola_Monitor, Productor y
class Cola_Monitor
{
int n;
boolean valueSet = false; synchronized int get( ) {
if (!valueSet) try wait( );
catch(InterruptedException e);
System.out.println (“Obtenido: “ + n); valueSet = false;
notify( ); return n; }
}
synchronized int put(int n) {
if (valueSet) try wait( );
catch(InterruptedException e);
this.n = n;
valueSet = true; System.out.println (“Colocado: “ + n); notify( );
class Producer implements Runnable {
Q q;
Producer (Q q) {
this.q = q;
new Thread(this, “Producer”).start( ); }
public void run( ) {
int i = 0; while(true)
{ q.put( i++ ); } }
}
class Consumer implements Runnable
{ Q q;
Producer (Q q) {
this.q = q;
new Thread(this, “Consumer”).start( ); }
public void run( ) {
while(true) { q.get( ); } }
class ProCon
{
public static void main(String args[ ] )
{
Cola_Monitor q = new Cola_Monitor( );
new Productor (q);
Actividad Individual
Elabora el programa de Hola Mundo usando
monitores, donde Hilo 0 imprima «hola» y el
hilo1 imprima «mundo».
¿Cuál es la diferencia entre el programa de
Hola_ Mundo con semáforos y Hola_Mundo
con monitores?
¿Cuál de los dos mecanismos se te facilito
implementar’
Realiza el reporte incluyendo los dos
class Monitor_HolaM
{
private String cad;
private boolean disponible = true;
public Monitor_HolaM(){ cad=null;
}
public synchronized void imprime1(String cad) {
while (disponible == false) {
try {
wait();
} catch (Exception e){} }
System.out.println("Nombre: "+ Thread.currentThread().getName()+ cad);
disponible = false; notify();
}
public synchronized void
imprime2(String cad) {
while (disponible == true) {
try {
wait();
class P1 implements Runnable{ Monitor_HolaM m; String name; public P1(String name,Monitor_HolaM m){ this.m = m;
this.name=name; }
public void run(){ for(int i=0;i<3;i++) { m.imprime1(" Hola "); } } }
class P2 implements Runnable{ Monitor_HolaM m;
String name;
public P2(String
name,Monitor_HolaM m){ this.m = m;
this.name=name; }
public void run(){
for(int i=0;i<3;i++) {
m.imprime2(" Mundo ");
Clase Principal
public class HolaM {
public static void main(String[] args) {
Monitor_HolaM m = new Monitor_HolaM(); P1 p1= new P1("hilo1: ",m);
P2 p2= new P2("hilo2: ",m); Thread a = new Thread(p1); Thread b = new Thread(p2); a.start();
b.start();
Ejemplo 3 Problema de los Jardines
Existen en N hilos que compiten por entrar por
una puerta para incrementar un valor
Exclusión Mutua
class Cont {
private int c = 0;
public synchronized void inc(int i){ c++;
System.out.println("Nombre: " + Thread.currentThread().getName() +" " + c);
}
public synchronized int valor(){
return c; }
}
class puerta implements Runnable{ Cont c;
public puerta(Cont c){this.c = c;} public void run(){
for (int i = 0; i<20; i++) c.inc(1);
} }
public class UsaJardines {
public static void main(String[] args){ final int N = 10;
Cont c = new Cont();
puerta[] p = new puerta[N];
for (int i = 0; i< N; i++) p[i] = new puerta(c); Thread[] ph = new Thread[N];
for (int i = 0; i< N; i++) ph[i] = new Thread(p[i]);
for (int i = 0; i< N; i++) ph[i].start(); for (int i = 0; i< N; i++)
try{ph[i].join();
}catch (InterruptedException e){} System.out.println(c.valor()); }
Java no asigna ninguna estructura concreta a
las hebras que están en el conjunto de
entrada.
La implementación podría usar
Una FIFO
Una LIFO
Java: notify-and-continue
Java utiliza el mecanismo notify-and-continue,
es decir, el hilo que hace notify() continúa con
el lock del monitor. Por lo tanto, el hilo que
espera debe ejecutar un código del tipo:
while (!condicion)
try {wait();
Java y Variables de Condición
En Java podemos pensar que hay una
condición(declarada de manera implícita) por
cada objeto sincronizado.
Por lo cual debemos declarar los recursos
compartidos como objetos que tendrán
asociado una variable de condición para
verificar su estado.
Las variables tipos C
ondition
que solo
Variables tipos Condition
Valores
: No toma ningún tipo de valor, pero
tiene asociada una lista de procesos
suspendidos
Operaciones:
Delay , suspende el proceso que lo ejecuta y lo
incluye en la lista de una variable Condition.
Resume, Reactiva un proceso de la lista asociada
a una variable Condition.
empty , retorna True si la lista de procesos de la
Productor/Consumidor con condiciones y
bloqueo
public class Condition {
public Condition() {
}
public synchronized void delay(){
try{wait(); // suspende al hilo que lo ejecuta }catch (Exception e){};
}
public synchronized void resume(){
try{notify(); // despierta un hilo suspendido }catch (Exception e){};
}
public class BufferCond {
private int[] b; private int tam; private int i=0; private int j=0;
private int numDatos = 0;
private final Condition nolleno = new Condition();
private final Condition novacio = new Condition();
public BufferCond(int t) {
tam = t;
public synchronized void poner(int d) throws InterruptedException
{
while (numDatos == tam) nolleno.delay();
b[i] = d;
i = (i + 1); //% tam; numDatos++;
novacio.resume(); }
public synchronized int extraer() throws InterruptedException {
while (numDatos == 0) novacio.delay();
int aux = j;
public class ProductorCon implements Runnable{ Buffer2 b; int id; public ProductorCon(BufferCond b,int id){
this.b = b; this.id = id; }
public void run(){
System.out.println("comienza productor");
try {
b.poner(id);
} catch (Exception e){};
System.out.println("Productor: "+id +" puso: "+id);
}
}
public class ConsumidorCon implements Runnable
{
Buffer2 b; int id;
public
ConsumidorCon(BufferCond b, int id){ this.b = b;
this.id = id; }
public void run(){ int d = 0;
System.out.println("comienza consumidor " );
try{d = b.extraer();
} catch (Exception e){};
System.out.println("Consumidor: " + id+" extrajo " +d);
public class ProduConsCond {
public static void main(String[] args) {
BufferCond b = new BufferCond(10); for (int i = 0; i<10; i++)
{
ProductorCon pc = new ProductorCon(b,i); ConsumidorCon cc = new ConsumidorCon(b,i); //System.out.println("comienza el programa"); Thread ph = new Thread(pc);
Thread ch = new Thread(cc); ph.start();
ch.start();
} }
Múltiples productores y consumidores
Hay varios procesos productores y
consumidores.
Todos los procesos utilizan el buffer en
exclusión mutua.
Un productor no puede escribir hasta que no
hay sitio
en el buffer.
Los consumidores leen todos los datos
producidos
En el mismo orden. Si el buffer está vacío
esperan
1 2 3 4
C1 C2 C2
P 1
Locks
En Java 5.0, se introdujo un mecanismo de sincronización alternativo
al lock intrínseco que se define a través de la clase ReentrantLock y cuya funcionalidad se define a través de la interfaz Lock.
El ReentrantLock se introduce por las limitaciones del lock intrínseco:
No es posible interrumpir un thread que espera un wait.
No es posible intentar de forma no bloqueante adquirir un lock sin
suspenderse definitivamente en él.
Los lock intrínseco deben ser liberados en el mismo bloque de código
en el cual se suspendió.
Comparación:
El Lock intrínseco conduce a un estilo de programación sencillo,
seguro y compatible con la gestión de excepciones
El ReentrantLock conduce a estrategias menos seguras, pero mas
Interface java.util.concurrent.locks
public interface Lock{
//Provide more extensive locking operations than can be obtained using synchronized // methods and statements.
void lock();
// Acquires the lock
void lockInterruptibly() throws InterruptedExcetion;
// Acquires the lock unless the current thread is interrupted.
boolean tryLock();
// Acquires the lock only if it is free at the time of invocation.
boolean tryLock(long timeout,TimeUnit unit) throws InterruptedExcetion;
// Acquires the lock if it is free within the given waiting time and the current // thread has not been interrupted.
void unlock();
// Releases the lock.
Condition newCondition();
//Returns a new condiction instance that is bound to this Lock instance. }
La interfaz Lock define un conjunto de operaciones abstractas de toma y liberación
de un lock.
A diferencia del lock intrínseco, la interface Lock ofrece diferentes formas de toma
Objetos Condition
Cuando se utiliza un Lock explícito para definir
una región asíncrona, dentro de ella se
utilizan
los
objetos
Condition
como
mecanismo de sincronización entre threads.
Un objeto
Condition
está estructuralmente
ligado a un objeto Lock. Sólo puede crearse
invocando el método
newCondition()
sobre un
objeto Lock.
El objeto
Condition
solo puede ser invocado
Es ofrecida por variables de condición asociadas a un reentrantLock : public interface Condition {
void await();
//Causes the current thread to wait until it is signalled or interrupted. boolean await(long time, TimeUnit unit)
// Causes the current thread to wait until it is signalled or interrupted, or the specified
// waiting time elapses.
long awaitNanos (long nanosTimeout)
//Causes the current thread to wait until it is signalled or interrupted, or the specified
// waiting time elapses. void awaitUninterruptibly()
// Causes the current thread to wait until it is signalled. boolean awaitUntil(Date deadline)
// Causes the current thread to wait until it is signalled or interrupted, or the specified
// deadline elapses. void signal()
// Wakes up one waiting thread. void signalAll()
// Wakes up all waiting threads.
Locks
Los métodos/instrucciones synchronized modelan el acceso exclusivo a lock de un monitor implícito asociado a un objeto, típicamente un recurso compartido por varias hebras.
Sin embargo, cuando una hebra necesita usar más de un recurso, debe liberar los locks de los recursos en orden inverso a como se han obtenido, lo que en ocasiones puede no ser adecuado
....
A.acquire();
B.acquire();
C.acquire(); A.release();
Locks
public class ReentrantLock
ReentrantLock l = new ReentrantLock();
Un Lock para la exclusión mutua con la misma
Locks: el problema de los jardines
public class Cont {
Lock l = new ReentrantLock(); private int c = 0;
public void inc(int i){ l.lock();
try{ c++;
} finally {
l.unlock(); }
}
Implementación de la interfaz lock
Pido el lock
Locks: el problema de los jardines
public int valor(){ l.lock();
try{
return c; } finally {
l.unlock(); }
}
Pido el lock
Devuelvo el lock
Locks: condiciones
public interface Condition
Las condiciones clasifican los métodos del monitor (wait,
notify and notifyAll) en distintos objetos de forma que es
posible tener múltiples conjuntos de espera por objeto, asociados a locks.
Lock l = new ReentrantLock() Condition c1 = l.newCondition(); Condition c2 = l.newCondition();
Para modelar las condiciones de sincronización usamos la Interfaz Condition.
Locks: condiciones
Métodos
void await() throws InterruptedException
Suspende a la hebra en la condición correspondiente
void signal()
Despierta una de las hebras que espera. La hebra tiene que
conseguir el lock correspondiente antes de continuar su ejecución.
(disciplina signal-and-continue)
void signalAll()
Desperta a todas las hebras que esperan. Cada hebra tiene que conseguir el lock correspondiente antes de continuar su ejecución.
Locks: condiciones
Lock l = new ReentrantLock() Condition c1 = l.newCondition(); Condition c2 = l.newCondition();
l.lock(); try {
while (!condicion1)
c1.await(); // mientras !condicion1 espera en c1 // condicion1 se satisface
// cambia el estado del objeto y condicion2 es cierta c2.signal(); // despertar una hebra que espera
} finally {
Productor/Consumidor
package condicion;
import java.util.concurrent.locks.*; public class Buffer {
private int[] b; private int tam; private int i=0; private int j=0;
private int numDatos = 0;
private final ReentrantLock lockBuffer = new ReentrantLock();
private final Condition nolleno = lockBuffer.newCondition(); private final Condition novacio = lockBuffer.newCondition();
public Buffer(int t){ tam = t;
b = new int[tam]; }
Productor/Consumidor
public void poner(int d) throws InterruptedException{ System.out.println("productor quiere poner "+d); try{
lockBuffer.lock();
while (numDatos == tam) nolleno.await();
b[i] = d;
i = (i + 1) % tam; numDatos++; novacio.signal();
}finally{lockBuffer.unlock();} }
Productor/Consumidor
public int extraer() throws InterruptedException{
System.out.println("consumidor quiere extraer" + numDatos); try{
lockBuffer.lock();
while (numDatos == 0) novacio.await();
int aux = j;
j = (j + 1) % tam; numDatos--;
nolleno.signal(); return b[aux];
Barbero Dormilón
import java.util.concurrent.locks.*; public class Barberia {
private Lock BLock = new ReentrantLock();
private Condition cBlibre = BLock.newCondition();
private Condition cSillaOcupada = BLock.newCondition(); private Condition cPuertaAbierta = BLock.newCondition(); private Condition cSiguiente = BLock.newCondition();
private boolean Blibre = false;
Barbero Dormilón
public void siguiente(){ BLock.lock(); try{
System.out.println("Barbero libre"); Blibre = true;
cBlibre.signal();
while (!SillaOcupada)
try{cSillaOcupada.await();}
catch (InterruptedException e){}
}finally {
BLock.unlock(); }
Barbero Dormilón
public void finPelar(){ BLock.lock(); try{
PAbierta = true;
cPuertaAbierta.signal(); while (PAbierta)
try{cSiguiente.await();}
catch (InterruptedException e){};
}finally {
BLock.unlock(); }
Barbero Dormilón
public void qPelar(int i){ BLock.lock();
try{
while (!Blibre)
try{cBlibre.await();}
catch (InterruptedException e){}; Blibre = false;
SillaOcupada = true;
System.out.println("Cliente "+i+" se sienta en la silla"); cSillaOcupada.signal();
while (!PAbierta)
try{cPuertaAbierta.await();} catch (InterruptedException e){};
System.out.println("Cliente "+i+" se va"); PAbierta = false;
cSiguiente.signal(); }finally {
Barbero Dormilón
public void qPelar(int i){ BLock.lock();
try{
while (!Blibre)
try{cBlibre.await();}
catch (InterruptedException e){}; Blibre = false;
SillaOcupada = true;
System.out.println("Cliente "+i+" se sienta en la silla"); cSillaOcupada.signal();
while (!PAbierta)
try{cPuertaAbierta.await();} catch (InterruptedException e){};
System.out.println("Cliente "+i+" se va"); PAbierta = false;
cSiguiente.signal(); }finally {
Barbero Dormilón
public class Barbero implements Runnable { Barberia b;
public Barbero(Barberia b){ this.b = b;
}
public void run(){ while (true){
b.siguiente();
System.out.println("Barbero Pela Cliente"); // Barbero pela cliente
b.finPelar(); }
}
Barbero Dormilón
public class cliente implements Runnable{ Barberia b;
int id;
public cliente(Barberia b,int i){ this.b = b;
id = i; }
public void run(){ b.qPelar(id); }
Barbero Dormilón
public class UsaBarberia {
public static void main(String[] args){ final int N = 125;
Barberia b = new Barberia(); Barbero bar = new Barbero(b); cliente[] c = new cliente[N];
for (int i = 0; i<N; i++) c[i] = new cliente(b,i);
Thread bh = new Thread(bar); bh.start();
Thread[] ch = new Thread[N];
for (int i = 0; i<N; i++)
ch[i]= new Thread(c[i]);
for (int i = 0; i<N; i++) ch[i].start();
Investigar en Equipo
Cierres de exclusión mutua
java.util.concurrent.locks
• Interfaces Lock y Condition
– Lock permite gestionar los cierres de forma explícita • lock()
• unlock() • tryLock()
• newCondition()
Condition permite establecer más de una cola asociada a un cierre
• await()
• awaitUntil() • signal()