Threads de Control

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

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
ARTÍCULO ANTERIOR

SIGUIENTE ARTÍCULO