Programación Avanzada.
Centro Superior de Informática.
Universidad de La Laguna.
Productor/Consumidor.
Se puede trabajar con múltiples flujos de control de forma que los
threads sean asíncronos e independientes. Esto es, cada thread contiene
todos los datos y métodos que requiere para su ejecución
y no necesita recurso o métodos externos. Sin embargo, exiten
muchas situaciones interesantes en las que threads separados que se ejecutan
independientemente han de compartir datos y han de tener en cuenta el estado
y las actividades del resto de los threads. Un ejemplo de las segundas
situaciones son las conocidas como "Productor/Consumidor" en las que un
productor genera un flujo de datos que es absorvido por un consumidor.
Una de las principales dificultades de la utilización de threads
es el hecho de compartir datos. Cuando se ejecuta un método, es
posible que al thread actual se le acabe el tiempo que le han asignado
en medio de un método y que otro thread que ejecuta el mismo método
modificandole los valores que utiliza.
La aplicación
PCTest.java
contiene
la definición de una clase
Productor/Consumidor
en
la que se instancian dos objetos: uno de tipo Productor
y
otro de tipo Consumidor. Estos objetos son Threads que se
encargan uno de poner un valor y el otro de cogerlo
de un
Mostrador. El Productor
genera
un entero entre 0 y 9, lo almacena en el mostrador y lo imprime. El Consumidor
al
contrario, consume todos los enteros del mostrador (que es exactamente
el mismo objeto en el que el productor pone los enteros) tan pronto como
están disponibles. Así pues, el productor y el consumidor
de este ejemplo comparten los datos a través del objeto Mostrador.
-
Proponga una implementación de
la clase Mostrador, que proporcione métodos que permitan
poner
(put)
y
coger (get) un valor contenido en un objeto
de tipo mostrador. Compile la aplicación y compruebe la salida.
¿Cuanto
hay que esperar para poner un nuevo valor? Si el productor es más
rápido que el consumidor puede ocurrir que genere dos o más
números antes de que el consumidor tenga oportunidad de recoger
ninguno.
Productor
#1 put: 0
Productor
#1 put: 1
Productor
#1 put: 2
Productor
#1 put: 3
Productor
#1 put: 4
Productor
#1 put: 5
Productor
#1 put: 6
Productor
#1 put: 7
Productor
#1 put: 8
Productor
#1 put: 9
Consumidor
#1 got: 9
Consumidor
#1 got: 9
Consumidor
#1 got: 9
Consumidor
#1 got: 9
Consumidor
#1 got: 9
Consumidor
#1 got: 9
Consumidor
#1 got: 9
Consumidor
#1 got: 9
Consumidor
#1 got: 9
Consumidor
#1 got: 9
¿Cuantas
veces se puede coger un valor puesto en el mostrador? Puede ocurrir que
el consumidor sea más rápido que el productor y entonces
cosume el mismo número más de una vez.
Productor
#1 put: 0
Consumidor
#1 got: 0
Consumidor
#1 got: 0
Consumidor
#1 got: 0
Consumidor
#1 got: 0
Consumidor
#1 got: 0
Consumidor
#1 got: 0
Consumidor
#1 got: 0
Consumidor
#1 got: 0
Consumidor
#1 got: 0
Consumidor
#1 got: 0
Productor
#1 put: 1
Productor
#1 put: 2
Productor
#1 put: 3
Productor
#1 put: 4
Productor
#1 put: 5
Productor
#1 put: 6
Productor
#1 put: 7
Productor
#1 put: 8
Productor
#1 put: 9
A este
tipo de problemas se les denomina Condiciones de Carrera (Race Conditions).
Estas condiciones, surgen cuando múltiples threads asíncronos
se ejecutan intentando acceder al mismo objeto al mismo tiempo y proporcionan
un resultado erróneo.
Para simular
los dos comportamientos anteriores, haga inactivo al productor o al consumir
una cierta cantidad de tiempo (aleatoria).
Podemos
enunciar los problemas que se nos presentan de la siguiente manera:
-
Los threads Productor y Consumidor no
deberían acceder simultáneamente al mostrador.
-
El Productor debería de encontrar
alguna forma de comunicarle al Consumidor que el valor ya está listo
y el Consumidor debe de alguna manera indicar que el valor ya se ha retirado.
-
Para solucionar el primer problema utilice
la palabra reservada "synchronized" para declarar los métodos
poner
y
coger.
Esto hace que el acceso al objeto mostrador esté bloqueado para
los otros threads.
-
Para solucionar el segundo problema,
los threads también deberían ser capaces de notificar al
resto cuando han terminado su trabajo con el objeto compartido. Considere
una variable disponible que contenga un true cuando
un valor acaba de ser dejado en el mostrador pero no ha sido cogido y false
cuando el valor ha sido cogido pero no repuesto.
-
Compile su aplicación con estas
modificaciones y compruebe si funciona.
-
Para solucionar el segundo problema,
falta además de añadir la variable disponible,
el que el consumidor espere (
wait() ) hasta que el productor
ponga algo en el mostrador y se lo notifique ( notifyAll() )
a todo el mundo. De forma similar, el productor debe esperar antes de reemplazar
el valor en el mostrador hasta que el consumidor coja el valor que ya estaba
en el mostrador y se lo notifique al resto del mundo.
Bibliografía.
[1] Y. Daniel Liang. "Introduction to Java Programming".
QueE&T. 1999
[2] S. Davis. "Aprenda Java ya". McGraw-Hill. Microsoft Press. 1996.
[3] J. Gosling, H. McGilton. "The Java Language Enviroment".
A White Paper. Sun Microsystems. Java Soft. 1996.
[4] D. Flanagan. "Java en pocs Palabras". McGrawHill-O'Reilyy. 1998.
[5] J.F. Macary, C. Nicolas. "Programación Java". Eyrolles.