Las lecciones anteriores conten�an ejemplos con threads as�ncronos e independientes. Esto es, cada thread conten�a todos los datos y m�todos necesarios y no requerian recursos externos. Adem�s, los threads de esos ejemplos se ejecutaban en su propio espacio sin concernir sobre el estado o actividad de otros threads que se ejecutaban de forma concurrente.
Sin embargo, existen muchas situaciones interesantes donde ejecutar threads concurrentes que compartan datos y deban considerar el estado y actividad de otros threads. Este conjunto de situaciones de programaci�n son conocidos como escenarios 'productor/consumidor'; donde el productor genera un canal de datos que es consumido por el consumidor.
Por ejemplo, puedes imaginar una aplicaci�n Java donde un thread (el productor) escribe datos en un fichero mientras que un segundo thread (el consumidor) lee los datos del mismo fichero. O si tecleas caracteres en el teclado, el thread productor situa las pulsaciones en una pila de eventos y el thread consumidor lee los eventos de la misma pila. Estos dos ejemplos utilizan threads concurrentes que comparten un recurso com�n; el primero comparte un fichero y el segundo una pila de eventos.
Como los threads comparten un recurso com�n, deben sincronizarse de alguna forma.
Esta lecci�n ense�a la sincronizaci�n de threads Java mediante un sencillo ejemplo de productor/consumidor.
�El Ejemplo Productor/Consumidor
El Productor genera un entero entre 0 y 9 (inclusive), lo almacena en un objeto "CubbyHole", e imprime el n�mero generado. Para hacer m�s interesante el problema de la sincronizaci�n, el prodcutor duerme durante un tiempo aleatorio entre 0 y 100 milisegundos antes de repetir el ciclo de generaci�n de n�meros.
class Producer extends Thread { private CubbyHole cubbyhole; private int number; public Producer(CubbyHole c, int number) { cubbyhole = c; this.number = number; } public void run() { for (int i = 0; i < 10; i++) { cubbyhole.put(i); System.out.println("Productor #" + this.number + " pone: " + i); try { sleep((int)(Math.random() * 100)); } catch (InterruptedException e) { } } } }
El Consumidor, est�ndo hambriento, consume todos los enteros de CubbyHole (exactamenten el mismo objeto en que el productor puso los enteros en primer lugar) tan r�pidamente como est�n disponibles.
class Consumer extends Thread { private CubbyHole cubbyhole; private int number; public Consumer(CubbyHole c, int number) { cubbyhole = c; this.number = number; } public void run() { int value = 0; for (int i = 0; i < 10; i++) { value = cubbyhole.get(); System.out.println("Consumidor #" + this.number + " obtiene: " + value); } } }
En este ejemplo el Productor y el Consumidor comparten datos a trav�s de un objeto CubbyHole com�n. Observar�a que ninguno de los dos hace ning�n esfuerzo sea el que sea para asegurarse de que el consumidor obtiene cada valor producido una y s�lo una vez. La sincronizaci�n entre estos dos threads realmente ocurre a un nivel inferior, dentro de los m�todos get() y put() del objeto CubbyHole. Sin embargo, asumamos por un momento que estos dos threads no est�n sincronizados y veamos los problemas potenciales que podr�a provocar esta situaci�n.
Un problema ser�a cuando el Productor fuera m�s r�pido que el Consumidor y generara dos n�meros antes de que el Consumidor tuviera una posibilidad de consumir el primer n�mero. As� el Consumidor se saltar�a un n�mero. Parte de la salida se podr�a parecer a esto.
. . . Consumidor #1 obtiene: 3 Productor #1 pone: 4 Productor #1 pone: 5 Consumidor #1 obtiene: 5 . . .
Otro problema podr�a aparecer si el consumidor fuera m�s r�pido que el Productor y consumiera el mismo valor dos o m�s veces. En esta situaci�n el Consumidor imprimir� el mismo valor dos veces y podr�a producir una salida como esta.
. . . Productor #1 pone: 4 Consumidor #1 obtiene: 4 Consumidor #1 obtiene: 4 Productor #1 pone: 5 . . .
De cualquier forma, el resultado es err�neo. Se quiere que el consumidor obtenga cada entero producido por el Productor y s�lo una vez. Los problemas como los escritos anteriormente,se llaman condiciones de carrera. Se alcanzan cuando varios threads ejecutados as�ncronamente intentan acceder a un mismo objeto al mismo tiempo y obtienen resultados err�neos.
Para prevenir estas condiciones en nuestro ejemplo Productor/Consumidor, el almacenamiento de un nuevo entero en CubbyHole por el Productor debe estar sincronizado con la recuperaci�n del entero por parte del Consumidor. El Consumidor debe consumir cada entero exactamente una vez. El programa Productor/Consumidor utiliza dos mecanismos diferentes para sincronizar los threads Producer y Consumer; los monitores, y los m�todos notify() y wait().
�Monitores
Los objetos, como el CubbyHole que son compartidos entre dos threads y cuyo acceso debe ser sincronizado son llamados condiciones variables. El lenguaje Java permite sincronizar threads alrededor de una condici�n variable mediante el uso de monitores. Los monitores previenen que dos threads accedan simult�neamente a la misma variable.
�Los m�todos notify() y wait()
En un nivel superior, el ejemplo Productor/Consumidor utiliza los m�todos notify() y wait() del objeto para coordinar la activadad de los dos threads. El objeto CubyHole utiliza notify() y wait() para asegurarse de que cada valor situado en �l por el Productor es recuperado una vez y s�lo una por el Consumidor.
�El programa Principal
Aqu� tienes una peque�a aplicaci�n Java que crea un objeto CubbyHole, un Producer, un Consumer y arranca los dos threads.
class ProducerConsumerTest { public static void main(String[] args) { CubbyHole c = new CubbyHole(); Producer p1 = new Producer(c, 1); Consumer c1 = new Consumer(c, 1); p1.start(); c1.start(); } }
�La Salida
Aqu� tienes la salida del programa ProducerConsumerTest.
Producer #1 pone: 0 Consumidor #1 obtiene: 0 Productor #1 pone: 1 Consumidor #1 obtiene: 1 Productor #1 pone: 2 Consumidor #1 obtiene: 2 Productor #1 pone: 3 Consumidor #1 obtiene: 3 Productor #1 pone: 4 Consumidor #1 obtiene: 4 Productor #1 pone: 5 Consumidor #1 obtiene: 5 Productor #1 pone: 6 Consumidor #1 obtiene: 6 Productor #1 pone: 7 Consumidor #1 obtiene: 7 Productor #1 pone: 8 Consumidor #1 obtiene: 8 Productor #1 pone: 9 Consumidor #1 obtiene: 9