1. Implementaremos ahora un ejercicio para practicar diferentes aspectos sobre hilos y multiprogramación:
javac Ej2.java java Ej2
¿Cuántos hilos o flujos de ejecución hay en total? ¿Qué hace cada uno? (AYUDA: en todo programa al menos hay UN flujo de ejecución, que no es otro que el programa principal. Aparte, podemos tener los flujos secundarios (hilos) que queramos).
public Ej2() { MiHilo t = new MiHilo("Hilo 1"); MiHilo t2 = new MiHilo("Hilo 2"); MiHilo t3 = new MiHilo("Hilo 3"); t.start(); t2.start(); t3.start(); do { System.out.print("Hilo 1 = " + t.contador + ", Hilo 2 = " + t2.contador + ", Hilo 3 = " + t3.contador + "\r"); try { Thread.currentThread().sleep(100); } catch(InterruptedException e) { } } while (t.isAlive() || t2.isAlive() || t3.isAlive()); }
El bucle do...while lo modificamos también, y ahora nos muestra cuánto vale el contador de cada hilo cada vez. Dentro del bucle, hacemos que el hilo actual (es decir, el programa principal), se duerma cada 100 ms, para dejar sitio a los hilos en el procesador.
Ejecuta después el programa varias veces (3 o 4), y comprueba el orden en el que terminan los hilos. ¿Existe mucha diferencia de tiempo entre la finalización de éstos? ¿Por qué?
public Ej2() { MiHilo t = new MiHilo("Hilo 1"); MiHilo t2 = new MiHilo("Hilo 2"); MiHilo t3 = new MiHilo("Hilo 3"); t.setPriority(Thread.MIN_PRIORITY); t2.setPriority(Thread.NORM_PRIORITY); t3.setPriority(Thread.MAX_PRIORITY); t.start(); t2.start(); t3.start(); ... }
Observa si los hilos terminan en el orden establecido. ¿Existe esta vez
más diferencia de tiempos en el orden de finalización?
Observa los campos locales de la clase MiHilo: la variable contador y el campo nombre. A juzgar por lo que has visto en la ejecución de los hilos: ¿Podría modificar el hilo t2 el valor de estos campos para el hilo t3, es decir, comparten los diferentes hilos estos campos, o cada uno tiene los suyos propios? ¿Qué pasaría si los tres hilos intentasen modificar el campo valor de la clase principal Ej2, también tendrían una copia cada uno o accederían los tres a la misma variable?
2. En este ejercicio vamos a practicar con la sincronización entre múltiples hilos, resolviendo el problema de los productores y los consumidores. Vamos a definir 3 clases: el hilo Productor, el hilo Consumidor, y el objeto Recipiente donde el productor deposita el valor producido, y de donde el consumidor extrae los datos.
javac Ej3.java java Ej3
¿Funciona bien el programa? ¿Qué anomalía(s) detectas?
Vamos a añadir el código necesario en los métodos produce y consume para sincronizar el acceso a ellos. El comportamiento debería ser el siguiente:
public void produce(int valor) { /* Si hay datos disponibles esperar a que se consuman */ if(disponible) { try { wait(); } catch(InterruptedException e) { } } this.valor = valor; /* Ya hay datos disponibles */ disponible = true; /* Notificar de la llegada de datos a consumidores a la espera */ notifyAll(); }¿Qué hace el método wait en este código?
public int consume() { /* Si no hay datos disponibles esperar a que se produzcan */ if(!disponible) { try { wait(); } catch(InterruptedException e) { } } /* Ya no hay datos disponibles */ disponible = false; /* Notificar de que el recipiente esta libre a productores en espera */ notifyAll(); return valor; }
Compilad y ejecutad el programa. ¿Qué excepción o error da? ¿A qué puede deberse?
El error del paso anterior lo da porque no podemos llamar a los
métodos wait o notify/notifyAll si no tenemos la variable
cerrojo. Dicha variable se consigue dentro de bloques de código synchronized,
de forma que el primer hilo que entra es quien tiene el cerrojo, y hasta que
no salga o se ponga a esperar, no lo liberará.
Para corregir el error, debemos hacer que tanto produce como consume
sean dos métodos synchronized, de forma que nos aseguremos que sólo
un hilo a la vez entrará, y podrá ejecutar dichos métodos.
public synchronized int consume() { ... } public synchronized void produce (int valor) { ... }
Compilar y comprobar que el programa funciona correctamente. Si lo ejecutáis varias veces, podría darse el caso de que aún salgan mensajes de consumición ANTES de que se muestren los de producción respectivos, por ejemplo:
Produce 0 Consume 0 Consume 1 Produce 1 Produce 2 Consume 2 ...
... pero aún así el programa es correcto. ¿A qué se debe entonces que puedan salir los mensajes en orden inverso? (AYUDA: observad el método System.out.println(...) que hay al final de los métodos run de Productor y Consumidor, que es el que muestra estos mensajes... ¿hay algo que garantice que se ejecute este método en orden?)
PARA ENTREGAR