Concurrencia Ejercicios Resueltos
Concurrencia Ejercicios Resueltos
Concurrencia Ejercicios Resueltos
Ejercicios resueltos
josé a. mañas
16.4.2015
Contenido
1 Introducción .......................................................................................................... 4
2 Ejercicios ................................................................................................................ 4
2.1 Semáforo ........................................................................................................ 4
2.2 Pestillo con cuenta atrás (count down latch) ................................................... 6
2.3 Contador compartido ...................................................................................... 8
2.4 Productor – Consumidor ................................................................................. 9
2.5 Readers & Writers......................................................................................... 11
2.6 Trenes ........................................................................................................... 14
2.7 Barrera.......................................................................................................... 16
2.8 Barrera Cíclica ............................................................................................... 16
2.9 Intercambiador (Exchanger<V>) .................................................................... 18
2.10 La cena de los filósofos (Dining philosophers) ............................................... 19
3 Soluciones............................................................................................................ 22
3.1 Semáforo ...................................................................................................... 22
3.2 Pestillo con cuenta atrás (count down latch) ................................................. 22
3.3 Contador compartido .................................................................................... 23
3.3.1 Solución usando un monitor .................................................................. 23
3.3.2 Solución usando un objeto atómico de java ........................................... 23
3.4 Productor – Consumidor ............................................................................... 23
3.4.1 Solución programada ............................................................................. 23
3.4.2 Solución usando la biblioteca de java ..................................................... 24
3.5 Readers & Writers......................................................................................... 26
3.5.1 Solución 1 .............................................................................................. 26
3.5.2 Solución 2: ordena los escritores............................................................ 27
3.6 Trenes ........................................................................................................... 28
3.6.1 Ejercicio 1 .............................................................................................. 28
3.6.2 Ejercicio 2 .............................................................................................. 29
3.6.3 Ejercicio 3 .............................................................................................. 30
3.6.4 Ejercicio 4 .............................................................................................. 32
3.7 Barrera.......................................................................................................... 33
3.8 Barrera Cíclica ............................................................................................... 34
3.8.1 Solución con contadores: ....................................................................... 34
3.8.2 Solución con conjuntos .......................................................................... 35
3.9 Intercambiador (Exchanger<V>) .................................................................... 36
3.10 La cena de los filósofos (Dining philosophers) ............................................... 37
4 Tareas en background .......................................................................................... 38
4.1 Esto no funciona ........................................................................................... 38
4.2 Usando join ................................................................................................... 38
4.3 Programando la tarea auxiliar como monitor ................................................ 39
4.4 Con un contador pestillo ............................................................................... 40
4.5 Modelo productor-consumidor ..................................................................... 41
4.6 Usando ejecutores de tareas y promesas de futuro ...................................... 42
5 Exámenes ............................................................................................................ 44
5.1 Mayo 2012 .................................................................................................... 44
5.2 Junio 2012 .................................................................................................... 44
5.3 Julio 2012...................................................................................................... 45
5.4 Abril 2013 ..................................................................................................... 45
5.5 Junio 2013 .................................................................................................... 46
5.6 Julio 2013...................................................................................................... 47
5.7 Abril 2014 ..................................................................................................... 47
5.8 Julio 2014...................................................................................................... 48
5.9 Abril 2015 ..................................................................................................... 48
6 Soluciones a los exámenes ................................................................................... 49
6.1 Mayo 2012 .................................................................................................... 49
6.2 Junio 2012 .................................................................................................... 50
6.3 Julio 2012...................................................................................................... 51
6.4 Abril 2013 ..................................................................................................... 52
6.5 Junio 2013 .................................................................................................... 53
6.6 Julio 2013...................................................................................................... 54
6.7 Abril 2014 ..................................................................................................... 56
6.8 Julio 2014...................................................................................................... 57
6.9 Abril 2015 ..................................................................................................... 57
1 Introducción
Ejercicios clásicos y no tan clásicos de concurrencia resueltos en java usando
monitores.
Los ejercicios están ordenados de forma un poco arbitraria. Quizás empezando por los
más fáciles y se van complicando poco a poco. Pero a veces un planteamiento es
engañosamente sencillo y otras veces la solución es muy fácil.
2 Ejercicios
2.1 Semáforo
Java proporciona Semaphores. Poner estos objetos como ejercicio sólo tiene interés
académico.
acquire(n)
Bloquea a la thread llamante hasta que hay tantos permisos disponibles como
se solicitan. En ese momento, se descuentan los permisos solicitados y la
thread se desbloquea.
release(n)
La clase Sepahore de java tiene más métodos; pero vamos a implementar sólo esos
dos.
Escenario de prueba
private int x = 0;
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
if (x == 3 * CNT)
System.out.println("OK");
else
System.out.println("Race error!");
}
private class Agent extends Thread {
private final MySemaphore semaphore;
@Override
public void run() {
try {
for (int i = 0; i < CNT; i++) {
semaphore.acquire();
x = x + 1;
semaphore.release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Si el semáforo no hace bien su función, las threads terminan diciendo “race condition!”
que quiere decir que se ha dado un caso de carrera o modificación descontrolada de
una variable compartida: x.
public MyCountDownLatch(int n) {
this.n = n;
}
System.out.println("before");
startSignal.countDown();
for (int i = 0; i < N; i++)
System.out.print('.');
doneSignal.await();
System.out.println();
System.out.println("after");
}
}
public class Worker implements Runnable {
private final int id;
private final MyCountDownLatch startSignal;
private final MyCountDownLatch doneSignal;
Worker(int id,
MyCountDownLatch startSignal,
MyCountDownLatch doneSignal) {
this.id = id;
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
void doWork() {
for (int i = 0; i < id; i++)
System.out.print(id);
}
}
Escenario de prueba:
int N = 10;
Robot[] robots = new Robot[N];
for (int i = 0; i < robots.length; i++)
robots[i] = new Robot(cc);
for (Robot robot : robots)
robot.start();
for (Robot robot : robots)
robot.join();
System.out.println("CC.getN(): " + cc.getSaldo());
}
@Override
public void run() {
nap();
cc.saca(1);
nap();
cc.mete(1);
}
}
public E take() {
E x = data[0];
nDatos--;
System.arraycopy(data, 1, data, 0, nDatos);
data[nDatos] = null;
return x;
}
}
Escenario de uso
@Override
public void run() {
while (true) {
n++;
System.out.println("P: " + n);
buffer.put(n);
nap(P_DELAY);
}
}
}
@Override
public void run() {
while (true) {
esperado++;
nap(C_DELAY);
int n = buffer.take();
System.out.println("C: " + n);
if (n != esperado)
System.out.println(
"C: ERROR: esperado " + esperado);
}
}
}
Escenario de pruebas.
} else {
data.openReading();
button.setBackground(READING);
nap(3);
data.closeReading();
}
}
}
ReaderWriter rw =
new ReaderWriter(data, button);
threads[row][col] = rw;
rw.start();
}
}
frame.pack();
frame.setVisible(true);
}
}
2.6 Trenes
Estos ejemplos se pueden animar usando la simulación de trenes en
http://www.dit.upm.es/~pepe/doc/adsw/trenes/
En abstracto, tenemos una zona de vía compartida por varios trenes y queremos
controlar la entrada de ternes por uno y otro lado
public class MonitorTunel
extends Monitor {
private final Tramo tramo1;
private final Tramo tramo2;
// la thread sale
public void salgo(Tren tren, Tramo tramo, Enlace salida) {
}
Ejercicio 1
El monitor debe controlar que en el tramo de vía compartido nunca hay más de
1 tren en cada momento. Es decir, o está vacío o está ocupado con 1 tren.
Cuando un tren quiere entrar y hay otro tren dentro, queda esperando hasta
que la vía esté libre.
Ejercicio 2
Al igual que el ejercicio 1, no puede haber más de un tren dentro. Pero además,
cuando un tren sale, tiene preferencia para entrar un tren que esté esperando
en dirección opuesta.
Ejercicio 3
2.7 Barrera
Una barrera de N posiciones retiene las primeras N-1 threads que llegan. Cuando llega
la enésima, permite que salgan todas. La barrera queda derruida y nuevas threads
pasan sin esperar.
public Barrier(int n) {
this.n = n;
}
Una barrera cíclica de N posiciones retiene las primeras N-1 threads que llegan.
Cuando llega la enésima, permite que salgan todas. Y empieza a contar para hacer otro
grupo de N threads.
public class MyCyclicBarrier {
private final int n;
public MyCyclicBarrier(int n) {
this.n = n;
}
Escenario de prueba
@Override
public void run() {
nap(3);
System.out.println(id + ": llega: "
+ new Date());
barrier.await();
System.out.println(id + ": sale: "
+ new Date());
}
}
2.9 Intercambiador (Exchanger<V>)
Dos agentes A y B se sincronizan en 1 punto. A le pasa un dato a B; B le pasa un dato a
A.
Escenario de prueba
@Override
public void run() {
while (true) {
nap();
int m = exchanger.exchange(this.n);
System.out.printf(
"agente %d: recibe %d%n", id, m);
n++;
}
}
}
2.10 La cena de los filósofos (Dining philosophers)
Es un problema clásico que se suele plantear en cursos de sistemas operativos, pero
que se aplica a situaciones donde unos pocos recursos se comparten entre unas pocas
tareas y los recursos se asignan poco a poco llevando a situaciones de bloqueo
(deadlock).
@Override
public void run() {
while (true) {
this.mode = Mode.THINKING;
nap(1);
this.mode = Mode.HUNGRY;
nap(1);
table.take(left, right);
this.mode = Mode.EATING;
nap(4);
table.leave(left, right);
}
}
Nótese que los filósofos disponen de un objeto compartido Table, que es lo que se
propone como ejercicio de programación de un monitor. Este monitor debe
implementar las operaciones atómicas de coger ambos tenedores o dejarlos, sin
posibilidad de coger o dejar sólo uno. Es decir, cada thread sólo progresa cuando tiene
ambos tenedores asidos.
public class Table {
3.1 Semáforo
public synchronized void acquire()
throws InterruptedException {
acquire(1);
}
private int n;
public MyCountDownLatch(int n) {
this.n = n;
}
class Productor
extends Thread {
private final BlockingQueue<Integer> buffer;
private int n = 0;
@Override
public void run() {
try {
while (true) {
n++;
System.out.println("P: " + n);
buffer.put(n);
nap(P_DELAY);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Consumidor extends Thread {
private final BlockingQueue<Integer> buffer;
private int esperado = 0;
@Override
public void run() {
try {
while (true) {
esperado++;
nap(C_DELAY);
int n = buffer.take();
System.out.println("C: " + n);
if (n != esperado)
System.out.println(
"C: ERROR: esperado " + esperado);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.5.1 Solución 1
Variables de estado:
int nWriters = 0
Lleva cuenta de cuántos agentes están leyendo ahora mismo.
int nReaders = 0
Lleva cuenta de cuántos escritores están escribiendo ahora mismo. Esta
variable sólo puede valer 0 o 1, y podría sustituirse por un boolean.
int nWritersWaiting = 0
Lleva cuenta de cuántos lectores están esperando para leer.
Variables de estado:
int nWriters = 0
Lleva cuenta de cuántos agentes están leyendo ahora mismo.
int nReaders = 0
Lleva cuenta de cuántos escritores están escribiendo ahora mismo. Esta
variable sólo puede valer 0 o 1, y podría sustituirse por un boolean.
List<ReaderWriter> writersWaiting
Lista de los agentes que están esperando a escribir. Se añaden al final según
llegan y se sacan al principio según se les atiende. El efecto es una cola FIFO.
3.6 Trenes
3.6.1 Ejercicio 1
Sólo se admite 1 tren dentro en cada momento. Los demás, esperan.
Variables de estado:
3.6.2 Ejercicio 2
Sólo puede haber un tren dentro; pero se hace un reparto equitativo de quién puede
entrar alternando la entrada por uno u otro tramo.
Variables de estado:
int esperando1 = 0;
Lleva la cuenta de cuántos trenes hay esperando entrar por el tramo 1.
int esperando2 = 0;
Lleva la cuenta de cuántos trenes hay esperando entrar por el tramo 2.
int ultimaEntrada = 1;
Memoriza si la última entrada fue por el tramo 1 o por el tramo 2.
public class MonitorTunel_2
extends Monitor {
private final Tramo tramo1;
private final Tramo tramo2;
3.6.3 Ejercicio 3
Se permiten varios trenes dentro, si circulan en la misma dirección.
Variables de estado:
int ocupado12 = 0
Lleva cuenta de cuántos trenes hay circulando que entraron por el tramo 1 en
dirección al tramo 2.
int ocupado21 = 0
Lleva cuenta de cuántos trenes hay circulando que entraron por el tramo 2 en
dirección al tramo 1.
Variables de estado:
int ocupado12 = 0
Lleva cuenta de cuántos trenes hay circulando que entraron por el tramo 1 en
dirección al tramo 2.
int ocupado21 = 0
Lleva cuenta de cuántos trenes hay circulando que entraron por el tramo 2 en
dirección al tramo 1.
int esperando1 = 0
Lleva cuenta de cuántos trenes hay esperando a entrar por el tramo 1.
int esperando2 = 0
Lleva cuenta de cuántos trenes hay esperando a entrar por el tramo 2.
int ultimaEntrada = 1
Memoriza por dónde entró el último tren, para alterna.
3.7 Barrera
Variables de estado:
int waiting = 0
Lleva cuenta de cuántas tareas han llegado a la barrera
boolean broken = false
Pasa a TRUE cuando se rompe la barrera y ya no hay que controlar más threads.
public Barrier(int n) {
this.n = n;
}
int waitingIn = 0;
int waitingOut = 0;
if (waitingIn >= n) {
waitingOut = n;
waitingIn -= n;
}
waitingOut--;
notifyAll();
}
Variables de estado:
if (incomingGroup.size() == n) {
outgoingGroup.addAll(incomingGroup);
incomingGroup.clear();
}
outgoingGroup.remove(me);
notifyAll();
}
int in = 0
Lleva la cuenta de cuántas threads han entrado a sincronizar. Al principio hay 0;
cuando llega a 2, pasamos a liberar las tareas y regresamos a 0.
int out = 2
Lleva la cuenta de cuántas tareas han salido. Cuando in llega a 2, out se pone a
0 y según van saliendo, se incrementa hasta llegar a 2. Se inicializa a 2
indicando que no queda nada por sacar.
private int in = 0;
private int out = 2;
private V[] dato = (V[]) new Object[2];
dato[in++] = x;
if (in == 2)
out = 0; // suficientes entradas
while (in < 2)
waiting(); // necesitamos mas entradas
V d = dato[out];
dato[out] = null;
out++;
if (out == 2)
in = 0;
notifyAll();
return d;
}
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
result = "done";
}
Permite que varias thread actúen como clientes. El resultado sólo se calcula una vez y
todos los clientes esperan ordenadamente para recogerlo.
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("end: " + new Date());
this.result = "done";
}
Permite que varias thread actúen como clientes. El resultado sólo se calcula una vez y
todos los clientes esperan ordenadamente para recogerlo.
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("end: " + new Date());
setResult("done");
}
Permite que varias thread actúen como clientes. El resultado sólo se calcula una vez y
todos los clientes esperan ordenadamente para recogerlo.
latch.await();
String result = task.getResult();
System.out.println("result: " + result);
}
}
public class BgTask03
extends Thread {
private final CountDownLatch latch;
private String result;
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
result = "done";
latch.countDown();
}
No permite que varias thread actúen como clientes. El resultado sólo se calcula una
vez y el primer cliente se lo lleva.
this.queue = queue;
}
@Override
public void run() {
try {
Thread.sleep(5000);
queue.put("done");
} catch (InterruptedException e) {
}
}
Permite que varios clientes compartan el resultado. El resultado sólo se calcula una vez
y todos esperan ordenadamente antes de recogerlo.
Se pide desarrollar una clase monitor GestorPuente que gestione el acceso al puente,
según la especificación previa. Los métodos de esta clase no retornan valores. El
esqueleto de la clase es el siguiente:
public class GestorPuente { ...
. . . void entrarNorte () { . . .}
. . . void entrarSur () { . . .}
. . . void entrarAmbulancia () { . . .}
. . . void salirPuente(){. . .}
}
Si una persona jubilada intenta entrar, tendrá prioridad frente al resto de personas que
estén esperando.
Cada persona se representa mediante una hebra. Además, hay una hebra que mide
periódicamente la temperatura de la sala y notifica su valor al sistema. Se pide
desarrollar un monitor (GestorSala) que sincronice a las hebras que representan
personas y a la hebra que mide la temperatura, de acuerdo con las especificaciones
anteriores. El monitor debe proporcionar los siguientes métodos:
class Monitor {
...
... requiereR1 (...) { ... }
... requiereR2_R3 (...) { ... }
... requiereR1_R2_R3 ( ...) { ...}
... liberaR1(...) { ... }
... liberaR2_R3 (...) { ... }
... liberaR1_R2_R3 ( ...) { ...}
}
Los aviones al despegar generan turbulencias, por lo que entre dos despegues
consecutivos tiene que transcurrir un intervalo de tiempo mínimo:
class GestorDespegue {
...
... despegarAvion() {...}
// lo invoca un avión cuando quiere despegar
}
Para gestionar este intervalo de tiempo, se dispone de una clase Temporizador, cuya
interfaz se muestra a continuación. El método iniciarTemporizador arranca un
temporizador que deja pasar un cierto tiempo. Cuando el tiempo expira, se invoca el
método autorizarDespegue del objeto de la clase GestorDespegue que se pasa en el
constructor. No es necesario desarrollar esta clase.
class GestorGaraje {
...
... GestorGaraje (int numPlazas) {...}
... entraCochePorEste (...) {...}
... entraCochePorOeste (...) {...}
... saleCoche (...) {...}
}
... cambiaSemáforos()
// lo invoca la hebra de control
...
}
...class Sincronizador {
...
int numero = 0
número siguiente
int nCochesNorte = 0
Indica el número de coches que están esperando para entrar en el puente por
el Norte
int nCochesSur = 0
Indica el número de coches que están esperando para entrar en el puente por
el Sur
int nPersonas = 0
Número de personas en la sala
int nJubilados = 0
Número de jubilados pendientes de entrar
boolean pistaOcupada
TRUE si hay un avión o una avioneta en pista
int nAvionesEsperando = 0
Número de aviones en cola para despegar.
boolean anteriorAvioneta = false
TRUE si lo último que despegó fue una avioneta.
int numPlazas
Número de plazas en el aparcamiento. Es un valor constante.
int numCoches
Lleva cuenta del número de coches aparcados.
boolean prioridadE
TRUE si tiene prioridad para entrar un coche que llegue por el ESTE.
FALSE si tiene prioridad para entrar un coche que llegue por el OESTE.
int esperandoE
Lleva cuenta del número de coches esperando para entrar por el ESTE.
int esperandoW
Lleva cuenta del número de coches esperando para entrar por el OESTE.
class GestorGaraje {
private final int numPlazas;
private int numCoches = 0;
private boolean prioridadE = true;
private int esperandoE = 0;
private int esperandoW = 0;
GestorGaraje(int numPlazas) {
this.numPlazas = numPlazas;
}
int cantidadAlmacen
Número de piezas en el almacén en un momento dado.
boolean peticionPendinte
TRUE si hay un cliente esperando a ser servido.
cantidadAlmacen -= cantidadPiezas;
peticionPendiente = false;
notifyAll();
}
int nPatas = 0
Número de patas en el almacén en un momento dado.
int nTableros = 0
Número de tableros en el almacén en un momento dado.